feat: establish first-class lexical language parity #2
5 changed files with 95 additions and 12 deletions
feat(scan): separate C and C++ architecture forms
commit
664e503719
|
|
@ -9,14 +9,19 @@ The first-class parity targets are Dart, Rust, C/C++, and
|
|||
JavaScript/TypeScript. Python and Swift remain secondary until their extraction
|
||||
contracts and fixtures reach the same standard.
|
||||
|
||||
Plain `.h` files remain classified as C++ because their language cannot be
|
||||
determined from the extension alone. C projects that require header symbol
|
||||
resolution need compilation-database or include-context evidence in a future
|
||||
frontend; Brick does not guess that ownership.
|
||||
|
||||
## Current coverage
|
||||
|
||||
| Language | Useful current coverage | Important gaps | Tier |
|
||||
| --- | --- | --- | --- |
|
||||
| Dart | Classes, mixins, enums, extensions, methods, named constructors, accessors, import/export/part directives, `extends`/`with`/`implements`/`on`, and ordinary calls | operators, generic relationship targets, conditional directives, and multiline declarations | first-class target |
|
||||
| Rust | Structs, enums, traits, ordinary/async/const/unsafe/extern functions, simple inherent/trait impl ownership, ordinary/turbofish/macro calls, braced `use` resolution, and raw-string masking | module ownership, unions/type aliases, nested generic impl headers, glob imports, and macro definitions | first-class target |
|
||||
| C | Files, basic includes, structs, and narrow function forms through the shared C/C++ profile | distinct C identity, enums/typedefs/function pointers, multiline declarations, macros, and declaration masking | first-class target |
|
||||
| C++ | Basic classes/structs, narrow function forms, calls, and local include resolution | templates, constructors/destructors/operators, multiple inheritance, namespaces, overloads, lambdas, qualifiers, and multiline declarations | first-class target |
|
||||
| C | Distinct `.c` identity, includes, structs/unions/enums, narrow function forms, and ordinary calls | anonymous typedef aggregates, function pointers, C23 attributes, macros, and multiline declarations | first-class target |
|
||||
| C++ | Classes/structs/unions/enums, `final`, constructors/destructors/operators, override-qualified methods, multiple inheritance, ordinary calls, and local include resolution | namespaces, overload disambiguation, lambdas, concepts/requires, complex template bases, and multiline declarations | first-class target |
|
||||
| JavaScript | Named functions, simple classes/extends, ES imports, and call-shaped expressions | methods, arrows/function expressions, CommonJS, dynamic imports, re-exports, decorators, and template literals | first-class target |
|
||||
| TypeScript | JavaScript basics plus TypeScript file extensions and simple imports | interfaces, type aliases, enums/namespaces, generic/abstract declarations, overloads, decorators, and path mappings | first-class target |
|
||||
| Python | Basic classes, functions/async functions, imports, calls, and indentation scopes | multiple bases, qualified ownership, decorators, multiline strings, relative/dynamic imports, and complete multi-import handling | secondary |
|
||||
|
|
|
|||
37
src/scan.rs
37
src/scan.rs
|
|
@ -316,7 +316,7 @@ impl ScanState {
|
|||
.flat_map(|names| names.as_str().split(','))
|
||||
.map(str::trim)
|
||||
.filter(|name| !name.is_empty())
|
||||
.map(clean_target)
|
||||
.map(|name| clean_relationship_target(name, language))
|
||||
.collect();
|
||||
if bases.is_empty()
|
||||
&& let Some(base) = captures.name("base")
|
||||
|
|
@ -406,7 +406,8 @@ impl ScanState {
|
|||
let member_call = prefix.ends_with('.') || prefix.ends_with("->");
|
||||
let definition_call_name = definition_name
|
||||
.as_deref()
|
||||
.and_then(|name| name.rsplit('.').next());
|
||||
.and_then(|name| name.rsplit('.').next())
|
||||
.map(|name| name.trim_start_matches('~'));
|
||||
if definition_call_name != Some(target) && !is_keyword(target) {
|
||||
self.pending.push(PendingRelationship {
|
||||
source: source.clone(),
|
||||
|
|
@ -645,6 +646,22 @@ impl Patterns {
|
|||
r"^\s*use\s+(?P<name>[A-Za-z_][\w:]*)",
|
||||
None,
|
||||
),
|
||||
"c" => (
|
||||
r"^\s*(?:typedef\s+)?(?:struct|union|enum)\s+(?P<name>[A-Za-z_]\w*)",
|
||||
vec![r"^\s*(?:[\w*&]+\s+)+(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*\{?\s*$"],
|
||||
r#"^\s*#\s*include\s*[<\"](?P<name>[^>\"]+)[>\"]"#,
|
||||
None,
|
||||
),
|
||||
"cpp" => (
|
||||
r"^\s*(?:class|struct|union|enum(?:\s+class)?)\s+(?P<name>[A-Za-z_]\w*)(?:\s+final)?",
|
||||
vec![
|
||||
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:(?:override|final)\s*)*(?:\{|=\s*(?:default|delete)\s*;)?\s*$",
|
||||
r"^\s*(?P<name>~?[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?::[^{}]*)?\s*(?:noexcept\s*)?(?:\{\s*\}|\{|=\s*(?:default|delete)\s*;)\s*$",
|
||||
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>operator\s*(?:[^\s(]+|\(\)))\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:\{|=\s*(?:default|delete)\s*;)\s*$",
|
||||
],
|
||||
r#"^\s*#\s*include\s*[<\"](?P<name>[^>\"]+)[>\"]"#,
|
||||
Some(r":\s*(?P<names>[^\{]+)"),
|
||||
),
|
||||
"javascript" | "typescript" => (
|
||||
r"^\s*(?:export\s+)?class\s+(?P<name>[A-Za-z_$][\w$]*)(?:\s+extends\s+(?P<base>[A-Za-z_$][\w$]*))?",
|
||||
vec![r"^\s*(?:export\s+)?(?:async\s+)?function\s+(?P<name>[A-Za-z_$][\w$]*)\s*\("],
|
||||
|
|
@ -707,7 +724,8 @@ fn language_for(path: &Path) -> Option<&'static str> {
|
|||
match path.extension()?.to_str()? {
|
||||
"rs" => Some("rust"),
|
||||
"py" => Some("python"),
|
||||
"c" | "h" | "cc" | "cpp" | "cxx" | "hh" | "hpp" | "hxx" => Some("cpp"),
|
||||
"c" => Some("c"),
|
||||
"h" | "cc" | "cpp" | "cxx" | "hh" | "hpp" | "hxx" => Some("cpp"),
|
||||
"js" | "jsx" | "mjs" | "cjs" => Some("javascript"),
|
||||
"ts" | "tsx" | "mts" | "cts" => Some("typescript"),
|
||||
"swift" => Some("swift"),
|
||||
|
|
@ -794,6 +812,19 @@ fn clean_import_target(value: &str, language: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn clean_relationship_target(value: &str, language: &str) -> String {
|
||||
let target = clean_target(value);
|
||||
if language == "cpp" {
|
||||
target
|
||||
.split_whitespace()
|
||||
.filter(|part| !matches!(*part, "public" | "protected" | "private" | "virtual"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
} else {
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
fn import_keys(name: &str, language: &str) -> Vec<String> {
|
||||
let mut keys = vec![name.to_owned()];
|
||||
match language {
|
||||
|
|
|
|||
9
tests/fixtures/languages/c/basic.c
vendored
9
tests/fixtures/languages/c/basic.c
vendored
|
|
@ -4,6 +4,15 @@ struct Worker {
|
|||
int state;
|
||||
};
|
||||
|
||||
union Payload {
|
||||
int integer;
|
||||
double decimal;
|
||||
};
|
||||
|
||||
enum State {
|
||||
ready
|
||||
};
|
||||
|
||||
int start() {
|
||||
return helper();
|
||||
}
|
||||
|
|
|
|||
13
tests/fixtures/languages/cpp/basic.cpp
vendored
13
tests/fixtures/languages/cpp/basic.cpp
vendored
|
|
@ -1,10 +1,19 @@
|
|||
#include "support.hpp"
|
||||
|
||||
class Worker : public Base {
|
||||
class Base {};
|
||||
class Runnable {};
|
||||
|
||||
template <typename T>
|
||||
class Worker final : public Base, private Runnable {
|
||||
public:
|
||||
void start() {
|
||||
Worker() {}
|
||||
~Worker() {}
|
||||
|
||||
void start() override {
|
||||
helper();
|
||||
}
|
||||
|
||||
Worker& operator=(const Worker&) = default;
|
||||
};
|
||||
|
||||
int helper() {
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
|||
},
|
||||
FixtureCase {
|
||||
directory: "c",
|
||||
language: "cpp",
|
||||
classes: &["Worker"],
|
||||
language: "c",
|
||||
classes: &["Payload", "State", "Worker"],
|
||||
functions: &["helper", "start"],
|
||||
imports: 1,
|
||||
inherits: 0,
|
||||
|
|
@ -52,10 +52,10 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
|||
FixtureCase {
|
||||
directory: "cpp",
|
||||
language: "cpp",
|
||||
classes: &["Worker"],
|
||||
functions: &["helper", "start"],
|
||||
classes: &["Base", "Runnable", "Worker"],
|
||||
functions: &["Worker", "helper", "operator=", "start", "~Worker"],
|
||||
imports: 1,
|
||||
inherits: 1,
|
||||
inherits: 2,
|
||||
calls: 1,
|
||||
},
|
||||
FixtureCase {
|
||||
|
|
@ -112,6 +112,35 @@ fn rust_fixture_models_impl_ownership_and_local_imports() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpp_fixture_models_member_ownership_without_declaration_calls() {
|
||||
let root = fixture_root("cpp");
|
||||
let building = scan(&root).unwrap();
|
||||
let worker = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::Class && node.name == "Worker")
|
||||
.unwrap();
|
||||
let members: Vec<_> = building
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|node| node.kind == NodeKind::Function && node.parent.as_ref() == Some(&worker.id))
|
||||
.map(|node| node.name.as_str())
|
||||
.collect();
|
||||
|
||||
assert_eq!(members.len(), 4);
|
||||
assert!(building.edges.iter().all(|edge| {
|
||||
edge.kind != EdgeKind::Calls
|
||||
|| !building.nodes.iter().any(|node| {
|
||||
node.id == edge.target
|
||||
&& matches!(
|
||||
node.name.as_str(),
|
||||
"Worker" | "~Worker" | "operator=" | "start"
|
||||
)
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
fn assert_fixture(case: FixtureCase) {
|
||||
let root = fixture_root(case.directory);
|
||||
let building = scan(&root).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue