ffmt 0.4.2__tar.gz → 0.4.4__tar.gz
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.
- {ffmt-0.4.2 → ffmt-0.4.4}/Cargo.lock +26 -26
- {ffmt-0.4.2 → ffmt-0.4.4}/Cargo.toml +1 -1
- {ffmt-0.4.2 → ffmt-0.4.4}/PKG-INFO +2 -2
- {ffmt-0.4.2 → ffmt-0.4.4}/README.md +1 -1
- {ffmt-0.4.2 → ffmt-0.4.4}/pyproject.toml +1 -1
- {ffmt-0.4.2 → ffmt-0.4.4}/src/case_norm.rs +32 -4
- {ffmt-0.4.2 → ffmt-0.4.4}/src/classifier.rs +129 -11
- {ffmt-0.4.2 → ffmt-0.4.4}/src/cli.rs +144 -39
- {ffmt-0.4.2 → ffmt-0.4.4}/src/config.rs +17 -11
- {ffmt-0.4.2 → ffmt-0.4.4}/src/formatter.rs +314 -146
- {ffmt-0.4.2 → ffmt-0.4.4}/src/keyword_norm.rs +31 -4
- {ffmt-0.4.2 → ffmt-0.4.4}/src/lsp.rs +14 -2
- ffmt-0.4.4/src/reader.rs +541 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/whitespace.rs +81 -19
- ffmt-0.4.4/tests/bugfixes.rs +788 -0
- ffmt-0.4.4/tests/bughunt_classifier.rs +357 -0
- ffmt-0.4.4/tests/bughunt_cli.rs +470 -0
- ffmt-0.4.4/tests/bughunt_norm.rs +185 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/classifier.rs +55 -0
- ffmt-0.4.4/tests/reader.rs +291 -0
- ffmt-0.4.2/src/reader.rs +0 -391
- ffmt-0.4.2/tests/bugfixes.rs +0 -366
- ffmt-0.4.2/tests/reader.rs +0 -139
- {ffmt-0.4.2 → ffmt-0.4.4}/.github/workflows/ci.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.github/workflows/release.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.github/workflows/update-readme-version.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.gitignore +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.markdownlint.json +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.pre-commit-hooks.yaml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/.readthedocs.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/LICENSE +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/action.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/ci.md +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/configuration.md +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/editors.md +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/getting-started.md +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/index.md +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/docs/requirements.txt +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/mkdocs.yml +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/align.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/lib.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/main.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/scope.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/src/unicode.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/case_norm.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/cli_bugs.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/config_integration.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/blank_lines.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/blank_lines.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/call_block.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/call_block.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/directives.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/directives.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/doxygen_spacing.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/doxygen_spacing.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/fypp.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/fypp.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/member_access_wrap.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/member_access_wrap.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/simple.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/simple.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/trailing_comment_wrap.expected.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/fixtures/trailing_comment_wrap.input.fpp +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/integration.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/scope.rs +0 -0
- {ffmt-0.4.2 → ffmt-0.4.4}/tests/whitespace.rs +0 -0
|
@@ -63,12 +63,12 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "bstr"
|
|
66
|
-
version = "1.12.
|
|
66
|
+
version = "1.12.3"
|
|
67
67
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
-
checksum = "
|
|
68
|
+
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
|
|
69
69
|
dependencies = [
|
|
70
70
|
"memchr",
|
|
71
|
-
"
|
|
71
|
+
"serde_core",
|
|
72
72
|
]
|
|
73
73
|
|
|
74
74
|
[[package]]
|
|
@@ -119,9 +119,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
|
119
119
|
|
|
120
120
|
[[package]]
|
|
121
121
|
name = "crossbeam-deque"
|
|
122
|
-
version = "0.8.
|
|
122
|
+
version = "0.8.7"
|
|
123
123
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
124
|
-
checksum = "
|
|
124
|
+
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
|
125
125
|
dependencies = [
|
|
126
126
|
"crossbeam-epoch",
|
|
127
127
|
"crossbeam-utils",
|
|
@@ -129,18 +129,18 @@ dependencies = [
|
|
|
129
129
|
|
|
130
130
|
[[package]]
|
|
131
131
|
name = "crossbeam-epoch"
|
|
132
|
-
version = "0.9.
|
|
132
|
+
version = "0.9.20"
|
|
133
133
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
134
|
-
checksum = "
|
|
134
|
+
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
|
135
135
|
dependencies = [
|
|
136
136
|
"crossbeam-utils",
|
|
137
137
|
]
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "crossbeam-utils"
|
|
141
|
-
version = "0.8.
|
|
141
|
+
version = "0.8.22"
|
|
142
142
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
143
|
-
checksum = "
|
|
143
|
+
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
|
144
144
|
|
|
145
145
|
[[package]]
|
|
146
146
|
name = "either"
|
|
@@ -156,7 +156,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
|
156
156
|
|
|
157
157
|
[[package]]
|
|
158
158
|
name = "ffmt"
|
|
159
|
-
version = "0.4.
|
|
159
|
+
version = "0.4.4"
|
|
160
160
|
dependencies = [
|
|
161
161
|
"clap",
|
|
162
162
|
"ignore",
|
|
@@ -194,9 +194,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "ignore"
|
|
197
|
-
version = "0.4.
|
|
197
|
+
version = "0.4.28"
|
|
198
198
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
199
|
-
checksum = "
|
|
199
|
+
checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7"
|
|
200
200
|
dependencies = [
|
|
201
201
|
"crossbeam-deque",
|
|
202
202
|
"globset",
|
|
@@ -232,15 +232,15 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
232
232
|
|
|
233
233
|
[[package]]
|
|
234
234
|
name = "log"
|
|
235
|
-
version = "0.4.
|
|
235
|
+
version = "0.4.33"
|
|
236
236
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
237
|
-
checksum = "
|
|
237
|
+
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|
238
238
|
|
|
239
239
|
[[package]]
|
|
240
240
|
name = "memchr"
|
|
241
|
-
version = "2.8.
|
|
241
|
+
version = "2.8.3"
|
|
242
242
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
243
|
-
checksum = "
|
|
243
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
244
244
|
|
|
245
245
|
[[package]]
|
|
246
246
|
name = "once_cell_polyfill"
|
|
@@ -259,9 +259,9 @@ dependencies = [
|
|
|
259
259
|
|
|
260
260
|
[[package]]
|
|
261
261
|
name = "quote"
|
|
262
|
-
version = "1.0.
|
|
262
|
+
version = "1.0.46"
|
|
263
263
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
264
|
-
checksum = "
|
|
264
|
+
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
|
265
265
|
dependencies = [
|
|
266
266
|
"proc-macro2",
|
|
267
267
|
]
|
|
@@ -288,9 +288,9 @@ dependencies = [
|
|
|
288
288
|
|
|
289
289
|
[[package]]
|
|
290
290
|
name = "regex"
|
|
291
|
-
version = "1.
|
|
291
|
+
version = "1.13.0"
|
|
292
292
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
293
|
-
checksum = "
|
|
293
|
+
checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
|
|
294
294
|
dependencies = [
|
|
295
295
|
"aho-corasick",
|
|
296
296
|
"memchr",
|
|
@@ -300,9 +300,9 @@ dependencies = [
|
|
|
300
300
|
|
|
301
301
|
[[package]]
|
|
302
302
|
name = "regex-automata"
|
|
303
|
-
version = "0.4.
|
|
303
|
+
version = "0.4.15"
|
|
304
304
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
305
|
-
checksum = "
|
|
305
|
+
checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
|
|
306
306
|
dependencies = [
|
|
307
307
|
"aho-corasick",
|
|
308
308
|
"memchr",
|
|
@@ -384,9 +384,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
|
384
384
|
|
|
385
385
|
[[package]]
|
|
386
386
|
name = "syn"
|
|
387
|
-
version = "2.0.
|
|
387
|
+
version = "2.0.118"
|
|
388
388
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
389
|
-
checksum = "
|
|
389
|
+
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
|
390
390
|
dependencies = [
|
|
391
391
|
"proc-macro2",
|
|
392
392
|
"quote",
|
|
@@ -491,6 +491,6 @@ dependencies = [
|
|
|
491
491
|
|
|
492
492
|
[[package]]
|
|
493
493
|
name = "zmij"
|
|
494
|
-
version = "1.0.
|
|
494
|
+
version = "1.0.23"
|
|
495
495
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
496
|
-
checksum = "
|
|
496
|
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ffmt
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -88,7 +88,7 @@ multdiv = false
|
|
|
88
88
|
# pre-commit
|
|
89
89
|
repos:
|
|
90
90
|
- repo: https://github.com/sbryngelson/ffmt
|
|
91
|
-
rev: v0.4.
|
|
91
|
+
rev: v0.4.3
|
|
92
92
|
hooks:
|
|
93
93
|
- id: ffmt
|
|
94
94
|
```
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
/// lowercased in non-opaque regions.
|
|
9
9
|
/// - All other text (identifiers, numbers, punctuation) is preserved.
|
|
10
10
|
pub fn normalize_case(line: &str) -> String {
|
|
11
|
+
normalize_case_with(line, false)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Normalize case of Fortran keywords to UPPERCASE (keyword-case = "upper").
|
|
15
|
+
pub fn normalize_case_upper(line: &str) -> String {
|
|
16
|
+
normalize_case_with(line, true)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fn normalize_case_with(line: &str, upper: bool) -> String {
|
|
11
20
|
let trimmed = line.trim_start();
|
|
12
21
|
|
|
13
22
|
// If the line is a Fypp directive, return unchanged.
|
|
@@ -107,9 +116,13 @@ pub fn normalize_case(line: &str) -> String {
|
|
|
107
116
|
// --- Dot operators: .TRUE., .AND., etc. ---
|
|
108
117
|
if ch == b'.' && i + 2 < len {
|
|
109
118
|
if let Some((dot_end, _)) = crate::match_dot_token(bytes, i) {
|
|
110
|
-
//
|
|
119
|
+
// Normalize the entire dot keyword/operator token
|
|
111
120
|
let op_str = &line[i..dot_end];
|
|
112
|
-
|
|
121
|
+
if upper {
|
|
122
|
+
out.push_str(&op_str.to_ascii_uppercase());
|
|
123
|
+
} else {
|
|
124
|
+
out.push_str(&op_str.to_ascii_lowercase());
|
|
125
|
+
}
|
|
113
126
|
i = dot_end;
|
|
114
127
|
continue;
|
|
115
128
|
}
|
|
@@ -123,14 +136,29 @@ pub fn normalize_case(line: &str) -> String {
|
|
|
123
136
|
}
|
|
124
137
|
let word = &line[start..i];
|
|
125
138
|
if is_keyword(word) {
|
|
126
|
-
|
|
139
|
+
if upper {
|
|
140
|
+
out.push_str(&word.to_ascii_uppercase());
|
|
141
|
+
} else {
|
|
142
|
+
out.push_str(&word.to_ascii_lowercase());
|
|
143
|
+
}
|
|
127
144
|
} else {
|
|
128
145
|
out.push_str(word);
|
|
129
146
|
}
|
|
130
147
|
continue;
|
|
131
148
|
}
|
|
132
149
|
|
|
133
|
-
// ---
|
|
150
|
+
// --- Non-ASCII: copy the whole UTF-8 character unchanged ---
|
|
151
|
+
// (`ch as char` would reinterpret each byte as Latin-1, mojibaking
|
|
152
|
+
// multi-byte sequences.)
|
|
153
|
+
if ch >= 0x80 {
|
|
154
|
+
if let Some(c) = line[i..].chars().next() {
|
|
155
|
+
out.push(c);
|
|
156
|
+
i += c.len_utf8();
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- Everything else (ASCII): pass through unchanged ---
|
|
134
162
|
out.push(ch as char);
|
|
135
163
|
i += 1;
|
|
136
164
|
}
|
|
@@ -141,6 +141,18 @@ fn find_matching_paren(s: &str, start: usize) -> Option<usize> {
|
|
|
141
141
|
|
|
142
142
|
/// Classify a Fortran line.
|
|
143
143
|
fn classify_fortran(trimmed: &str) -> LineKind {
|
|
144
|
+
// Strip an optional numeric statement label (e.g. "10 continue", "100 end if")
|
|
145
|
+
// so the underlying statement is classified. The label is kept in the output;
|
|
146
|
+
// stripping happens for classification only.
|
|
147
|
+
let (trimmed, had_numeric_label) = {
|
|
148
|
+
let re_num_label = re!(NUM_LABEL, r"^\d+\s+");
|
|
149
|
+
if let Some(m) = re_num_label.find(trimmed) {
|
|
150
|
+
(trimmed[m.end()..].trim_start(), true)
|
|
151
|
+
} else {
|
|
152
|
+
(trimmed, false)
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
144
156
|
// Strip optional label prefix like "outer: "
|
|
145
157
|
let line = {
|
|
146
158
|
let re_label = re!(LABEL, r"(?i)^\w+\s*:\s*");
|
|
@@ -212,8 +224,20 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
212
224
|
END_BLOCK,
|
|
213
225
|
r"(?i)^end\s*(if|do|select|subroutine|function|module|submodule|program|interface|type|block|associate|where|forall|enum|critical|team)\b"
|
|
214
226
|
);
|
|
215
|
-
if re_end_block.
|
|
216
|
-
|
|
227
|
+
if let Some(m) = re_end_block.find(line) {
|
|
228
|
+
// Fortran has no reserved words: "endif = 5" is an assignment to a
|
|
229
|
+
// variable named "endif", not a block close.
|
|
230
|
+
let rest = line[m.end()..].trim_start();
|
|
231
|
+
if !rest.starts_with('=') {
|
|
232
|
+
// A numeric-labeled "end do" may terminate a non-block labeled DO
|
|
233
|
+
// ("do 10 ... 10 end do"); since "do <label>" does not push an
|
|
234
|
+
// indentation scope, don't pop one here.
|
|
235
|
+
let re_end_do = re!(END_DO_LABELED, r"(?i)^end\s*do\b");
|
|
236
|
+
if had_numeric_label && re_end_do.is_match(line) {
|
|
237
|
+
return LineKind::FortranStatement;
|
|
238
|
+
}
|
|
239
|
+
return LineKind::FortranBlockClose;
|
|
240
|
+
}
|
|
217
241
|
}
|
|
218
242
|
// Bare "end" (possibly followed by comment)
|
|
219
243
|
let re_bare_end = re!(BARE_END, r"(?i)^end\s*(!.*)?$");
|
|
@@ -227,8 +251,8 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
227
251
|
if re_else_if.is_match(line) {
|
|
228
252
|
return LineKind::FortranContinuation;
|
|
229
253
|
}
|
|
230
|
-
// bare else (possibly followed by comment)
|
|
231
|
-
let re_bare_else = re!(BARE_ELSE, r"(?i)^else\s*(!.*)?$");
|
|
254
|
+
// bare else or named "else <construct-name>" (possibly followed by comment)
|
|
255
|
+
let re_bare_else = re!(BARE_ELSE, r"(?i)^else\b(\s+\w+)?\s*(!.*)?$");
|
|
232
256
|
if re_bare_else.is_match(line) {
|
|
233
257
|
return LineKind::FortranContinuation;
|
|
234
258
|
}
|
|
@@ -291,6 +315,14 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
291
315
|
}
|
|
292
316
|
}
|
|
293
317
|
|
|
318
|
+
// Non-block labeled DO ("do 10 i = 1, n") is terminated by the labeled
|
|
319
|
+
// statement ("10 continue"), which is classified as a plain statement, so
|
|
320
|
+
// it must NOT open an indentation scope.
|
|
321
|
+
let re_do_label = re!(DO_LABEL, r"(?i)^do\s+\d+\b");
|
|
322
|
+
if re_do_label.is_match(line) {
|
|
323
|
+
return LineKind::FortranStatement;
|
|
324
|
+
}
|
|
325
|
+
|
|
294
326
|
// do / do concurrent
|
|
295
327
|
let re_do = re!(DO_OPEN, r"(?i)^do\b");
|
|
296
328
|
if re_do.is_match(line) {
|
|
@@ -314,10 +346,11 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
314
346
|
return LineKind::FortranBlockOpen;
|
|
315
347
|
}
|
|
316
348
|
|
|
317
|
-
// function (with optional prefixes
|
|
349
|
+
// function (with optional prefixes; type-spec prefixes may carry a paren
|
|
350
|
+
// group with one level of nesting, e.g. type(point(8)), integer(kind=8))
|
|
318
351
|
let re_func = re!(
|
|
319
352
|
FUNC,
|
|
320
|
-
r"(?i)^(((pure|elemental|impure|recursive|integer|real|double\s+precision|complex|character|logical|type\s*\([^)]*\))\s+)*)function\b"
|
|
353
|
+
r"(?i)^((((pure|elemental|impure|recursive|module)|(integer|real|double\s+precision|complex|character|logical|type|class)(\s*\(([^()]|\([^()]*\))*\))?)\s+)*)function\b"
|
|
321
354
|
);
|
|
322
355
|
if re_func.is_match(line) {
|
|
323
356
|
return LineKind::FortranBlockOpen;
|
|
@@ -361,8 +394,8 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
361
394
|
return LineKind::FortranStatement;
|
|
362
395
|
}
|
|
363
396
|
// type is (...) was already handled in continuations above
|
|
364
|
-
// type :: name
|
|
365
|
-
let re_type_def = re!(TYPE_DEF, r"(?i)^type\
|
|
397
|
+
// type :: name, type::name, or type, attrs :: name -> definition (block open)
|
|
398
|
+
let re_type_def = re!(TYPE_DEF, r"(?i)^type\s*(::|,|\s)");
|
|
366
399
|
if re_type_def.is_match(line) {
|
|
367
400
|
return LineKind::FortranBlockOpen;
|
|
368
401
|
}
|
|
@@ -406,6 +439,12 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
406
439
|
}
|
|
407
440
|
}
|
|
408
441
|
|
|
442
|
+
// block data [name] (legacy program unit; "end block data" is a closer)
|
|
443
|
+
let re_block_data = re!(BLOCK_DATA, r"(?i)^block\s*data(\s+\w+)?\s*$");
|
|
444
|
+
if re_block_data.is_match(line) {
|
|
445
|
+
return LineKind::FortranBlockOpen;
|
|
446
|
+
}
|
|
447
|
+
|
|
409
448
|
// block (bare keyword)
|
|
410
449
|
if lower == "block" {
|
|
411
450
|
return LineKind::FortranBlockOpen;
|
|
@@ -441,6 +480,66 @@ fn classify_fortran(trimmed: &str) -> LineKind {
|
|
|
441
480
|
LineKind::FortranStatement
|
|
442
481
|
}
|
|
443
482
|
|
|
483
|
+
/// Split a Fortran line into its top-level statements at semicolons, ignoring
|
|
484
|
+
/// semicolons inside string literals and stopping at an inline comment.
|
|
485
|
+
/// Returns the trimmed pieces (empty pieces are kept so callers can skip them).
|
|
486
|
+
fn split_top_level_statements(line: &str) -> Vec<&str> {
|
|
487
|
+
let bytes = line.as_bytes();
|
|
488
|
+
let mut pieces = Vec::new();
|
|
489
|
+
let mut start = 0;
|
|
490
|
+
let mut in_string = false;
|
|
491
|
+
let mut quote = b' ';
|
|
492
|
+
let mut i = 0;
|
|
493
|
+
while i < bytes.len() {
|
|
494
|
+
let b = bytes[i];
|
|
495
|
+
if in_string {
|
|
496
|
+
if b == quote {
|
|
497
|
+
// Doubled quote is an escape — stay inside the string.
|
|
498
|
+
if i + 1 < bytes.len() && bytes[i + 1] == quote {
|
|
499
|
+
i += 2;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
in_string = false;
|
|
503
|
+
}
|
|
504
|
+
i += 1;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
match b {
|
|
508
|
+
b'\'' | b'"' => {
|
|
509
|
+
in_string = true;
|
|
510
|
+
quote = b;
|
|
511
|
+
}
|
|
512
|
+
b'!' => break, // inline comment — nothing after it is a statement
|
|
513
|
+
b';' => {
|
|
514
|
+
pieces.push(line[start..i].trim());
|
|
515
|
+
start = i + 1;
|
|
516
|
+
}
|
|
517
|
+
_ => {}
|
|
518
|
+
}
|
|
519
|
+
i += 1;
|
|
520
|
+
}
|
|
521
|
+
pieces.push(line[start..].trim());
|
|
522
|
+
pieces
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/// Net indentation effect of a line's top-level statements: +1 per block
|
|
526
|
+
/// opener, -1 per block closer, 0 for everything else (continuations,
|
|
527
|
+
/// statements). A self-contained single-line construct nets to 0.
|
|
528
|
+
fn net_block_delta(pieces: &[&str]) -> i32 {
|
|
529
|
+
let mut delta = 0;
|
|
530
|
+
for &piece in pieces {
|
|
531
|
+
if piece.is_empty() {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
match classify_fortran(piece) {
|
|
535
|
+
LineKind::FortranBlockOpen => delta += 1,
|
|
536
|
+
LineKind::FortranBlockClose => delta -= 1,
|
|
537
|
+
_ => {}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
delta
|
|
541
|
+
}
|
|
542
|
+
|
|
444
543
|
/// Classify a logical line.
|
|
445
544
|
pub fn classify(line: &str) -> LineKind {
|
|
446
545
|
let trimmed = line.trim();
|
|
@@ -479,7 +578,23 @@ pub fn classify(line: &str) -> LineKind {
|
|
|
479
578
|
}
|
|
480
579
|
|
|
481
580
|
// 6. Fortran
|
|
482
|
-
classify_fortran(trimmed)
|
|
581
|
+
let kind = classify_fortran(trimmed);
|
|
582
|
+
|
|
583
|
+
// A construct that opens AND closes on the same line (e.g.
|
|
584
|
+
// `do d = 1, n; s = s + d; end do`) has a net-zero indent effect. The
|
|
585
|
+
// leading keyword makes `classify_fortran` report a block opener, which
|
|
586
|
+
// would leak an indent level onto every following line. When the opener's
|
|
587
|
+
// matching closer is on the same line (net delta 0), treat the whole line
|
|
588
|
+
// as a plain statement instead — a block opener and a statement both sit at
|
|
589
|
+
// the current depth, so the line's own indentation is unchanged.
|
|
590
|
+
if kind == LineKind::FortranBlockOpen {
|
|
591
|
+
let pieces = split_top_level_statements(trimmed);
|
|
592
|
+
if pieces.len() > 1 && net_block_delta(&pieces) == 0 {
|
|
593
|
+
return LineKind::FortranStatement;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
kind
|
|
483
598
|
}
|
|
484
599
|
|
|
485
600
|
/// Extract the scope name from a Fortran block-opening line.
|
|
@@ -493,7 +608,10 @@ pub fn classify(line: &str) -> LineKind {
|
|
|
493
608
|
/// - `type :: my_type` -> Some("my_type")
|
|
494
609
|
/// - `type, extends(base) :: child` -> Some("child")
|
|
495
610
|
pub fn extract_scope_name(line: &str) -> Option<String> {
|
|
496
|
-
|
|
611
|
+
// Strip any trailing comment (string-aware) so names are never read from
|
|
612
|
+
// comment text (e.g. `program main ! function test`).
|
|
613
|
+
let stripped = strip_trailing_comment(line.trim());
|
|
614
|
+
let trimmed = stripped.as_str();
|
|
497
615
|
let lower = trimmed.to_ascii_lowercase();
|
|
498
616
|
|
|
499
617
|
// subroutine name(...) or subroutine name
|
|
@@ -508,7 +626,7 @@ pub fn extract_scope_name(line: &str) -> Option<String> {
|
|
|
508
626
|
// function name(...) or function name
|
|
509
627
|
let re_func = re!(
|
|
510
628
|
SCOPE_FUNC,
|
|
511
|
-
r"(?i)(?:(?:pure|elemental|impure|recursive|integer|real|double\s+precision|complex|character|logical|type
|
|
629
|
+
r"(?i)(?:(?:pure|elemental|impure|recursive|module|(?:integer|real|double\s+precision|complex|character|logical|type|class)(?:\s*\((?:[^()]|\([^()]*\))*\))?)\s+)*function\s+(\w+)"
|
|
512
630
|
);
|
|
513
631
|
if let Some(caps) = re_func.captures(trimmed) {
|
|
514
632
|
return Some(caps[1].to_string());
|