Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/src/fmt/mod.rs

15.5 KiB, 9 runs

created by r1870400018:11630, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Annealer -- the Hematite code formatter.
2//!
3//! A language-aware code formatter built on Wadler-style layout
4//! algebra. The architecture separates three concerns:
5//!
6//! - **Lexing**: source text to token stream (per-language, data-driven).
7//! - **Parsing**: token stream to concrete syntax tree (structural, keyword-aware).
8//! - **Formatting**: CST to layout document to formatted text (universal algebra).
9//!
10//! The layout algebra (the `Doc` type) is a small set of combinators
11//! that can express every formatting pattern across every language.
12//! The renderer walks the document and makes optimal line-breaking
13//! decisions within a given width.
14//!
15//! # Usage
16//!
17//! ```ignore
18//! use oxedyne_fe2o3_text::fmt::{format_rust, format_source, spec::FormatSpec};
19//!
20//! let source = "fn main ( ) { println!(\"hello\") ; }";
21//! let spec = FormatSpec::fe2o3();
22//! let formatted = res!(format_rust(source, &spec));
23//!
24//! // Language-generic path.
25//! let go_src = "func main() { fmt.Println(\"hello\") }";
26//! let formatted = res!(format_source(go_src, "go", &spec));
27//! ```
28//!
29pub mod doc;
30pub mod cst;
31pub mod lex;
32pub mod parse;
33pub mod spec;
34pub mod render;
35pub mod format;
36
37use crate::fmt::spec::FormatSpec;
38
39use oxedyne_fe2o3_core::prelude::*;
40
41
42/// Format Rust source code according to the given specification.
43pub fn format_rust(source: &str, spec: &FormatSpec) -> Outcome<String> {
44 format::format_rust(source, spec)
45}
46
47/// Format source code in the named language.
48///
49/// Supported languages: `"rust"`, `"c"`, `"cpp"` (also `"c++"`),
50/// `"csharp"` (also `"cs"`, `"c#"`), `"go"`, `"java"`,
51/// `"js"` (also `"javascript"`, `"typescript"`, `"ts"`),
52/// `"python"` (also `"py"`).
53///
54/// Rust goes through the structural parser for keyword-aware
55/// formatting. Other languages use the generic token-stream
56/// pipeline, which handles indentation, spacing, and comment
57/// preservation.
58pub fn format_source(source: &str, lang: &str, spec: &FormatSpec) -> Outcome<String> {
59 match lang {
60 "rust" | "rs" => format_rust(source, spec),
61 _ => {
62 let lang_tokens = res!(lang_tokens_for(lang));
63 format::format_with_lang(source, &lang_tokens, spec)
64 }
65 }
66}
67
68/// Detect language from a file path's extension.
69pub fn detect_language(path: &str) -> Option<&'static str> {
70 let ext = path.rsplit('.').next();
71 match ext {
72 Some("rs") => Some("rust"),
73 Some("c") => Some("c"),
74 Some("cpp") | Some("cxx") | Some("cc")
75 | Some("hpp") | Some("hxx") | Some("hh") => Some("cpp"),
76 Some("h") => Some("c"),
77 Some("cs") => Some("csharp"),
78 Some("go") => Some("go"),
79 Some("java") => Some("java"),
80 Some("js") | Some("jsx") | Some("mjs")
81 | Some("ts") | Some("tsx") => Some("js"),
82 Some("py") | Some("pyw") => Some("python"),
83 _ => None,
84 }
85}
86
87/// Detect language from source text content.
88///
89/// Examines the first 200 lines for shebang lines, keywords, and
90/// syntactic patterns distinctive to each supported language.
91/// Returns `None` if no language scores above the confidence
92/// threshold.
93pub fn detect_language_from_source(source: &str) -> Option<&'static str> {
94 // Shebang check.
95 if let Some(first) = source.lines().next() {
96 if first.starts_with("#!") {
97 let shebang = first.to_ascii_lowercase();
98 if shebang.contains("python") || shebang.contains("python3") {
99 return Some("python");
100 }
101 if shebang.contains("node") || shebang.contains("deno") || shebang.contains("bun") {
102 return Some("js");
103 }
104 if shebang.contains("java") {
105 return Some("java");
106 }
107 }
108 }
109
110 let mut rust: i32 = 0;
111 let mut c: i32 = 0;
112 let mut cpp: i32 = 0;
113 let mut csharp: i32 = 0;
114 let mut go: i32 = 0;
115 let mut java: i32 = 0;
116 let mut js: i32 = 0;
117 let mut python: i32 = 0;
118
119 let lines: Vec<&str> = source.lines().take(200).collect();
120
121 for line in &lines {
122 let trimmed = line.trim();
123
124 // Rust.
125 if trimmed.starts_with("pub fn ")
126 || trimmed.starts_with("pub struct ")
127 || trimmed.starts_with("pub enum ")
128 || trimmed.starts_with("pub trait ") { rust += 5; }
129 if trimmed.starts_with("fn ") { rust += 3; }
130 if trimmed.starts_with("let mut ") { rust += 4; }
131 if trimmed.starts_with("mod ") { rust += 2; }
132 if trimmed.starts_with("impl ") { rust += 5; }
133 if trimmed.starts_with("use ") && trimmed.contains("::"){ rust += 4; }
134 if trimmed.starts_with("#[") || trimmed.starts_with("#![") { rust += 4; }
135 if trimmed.starts_with("///") || trimmed.starts_with("//!") { rust += 3; }
136 if trimmed.contains("-> ") && !trimmed.starts_with("//") { rust += 2; }
137 if trimmed.contains("&self") { rust += 5; }
138 if trimmed.contains("&str") { rust += 4; }
139 if trimmed.contains("macro_rules!") { rust += 6; }
140 if trimmed.contains("res!(") || trimmed.contains("ok!(") { rust += 3; }
141 if trimmed.starts_with("match ") { rust += 2; }
142
143 // C.
144 if trimmed.starts_with("#include") { c += 6; }
145 if trimmed.starts_with("#define") { c += 4; }
146 if trimmed.starts_with("typedef ") { c += 5; }
147 if trimmed.contains("sizeof(") { c += 3; }
148 if trimmed.contains("malloc(") || trimmed.contains("free(") { c += 4; }
149 if trimmed.contains("printf(") || trimmed.contains("fprintf(") { c += 3; }
150 if trimmed.contains("NULL") { c += 2; }
151 if trimmed.starts_with("void ") { c += 2; }
152 if trimmed.contains("int main") { c += 3; }
153
154 // C++.
155 if trimmed.starts_with("#include <") && (
156 trimmed.contains("<iostream>") || trimmed.contains("<vector>")
157 || trimmed.contains("<string>") || trimmed.contains("<map>")
158 || trimmed.contains("<memory>") || trimmed.contains("<algorithm>")
159 || trimmed.contains("<functional>") || trimmed.contains("<array>")
160 || trimmed.contains("<optional>") || trimmed.contains("<variant>"))
161 { cpp += 6; }
162 if trimmed.starts_with("namespace ") { cpp += 5; }
163 if trimmed.starts_with("class ") && trimmed.contains('{') { cpp += 2; }
164 if trimmed.starts_with("template") { cpp += 6; }
165 if trimmed.contains("std::") { cpp += 5; }
166 if trimmed.contains("cout") || trimmed.contains("cerr") { cpp += 4; }
167 if trimmed.contains("nullptr") { cpp += 5; }
168 if trimmed.contains("auto ") && trimmed.contains("= ")
169 && !trimmed.starts_with("#") { cpp += 3; }
170 if trimmed.contains("const auto") { cpp += 4; }
171 if trimmed.contains("unique_ptr") || trimmed.contains("shared_ptr")
172 || trimmed.contains("make_unique") || trimmed.contains("make_shared")
173 { cpp += 6; }
174 if trimmed.contains("static_cast") || trimmed.contains("dynamic_cast")
175 || trimmed.contains("reinterpret_cast") { cpp += 6; }
176 if trimmed.starts_with("virtual ") { cpp += 4; }
177 if trimmed.contains("constexpr ") { cpp += 5; }
178 if trimmed.contains("noexcept") { cpp += 4; }
179 if trimmed.starts_with("using namespace") { cpp += 5; }
180 if trimmed.starts_with("#include") && !trimmed.contains('<') { cpp += 2; }
181 if trimmed.contains("override") && trimmed.contains('{') { cpp += 3; }
182
183 // C#.
184 if trimmed.starts_with("using System") { csharp += 6; }
185 if trimmed.starts_with("namespace ")
186 && !trimmed.contains("::") { csharp += 4; }
187 if trimmed.contains("Console.Write") { csharp += 5; }
188 if trimmed.starts_with("[") && (
189 trimmed.contains("Serializable") || trimmed.contains("Attribute")
190 || trimmed.contains("Test") || trimmed.contains("HttpGet")
191 || trimmed.contains("HttpPost") || trimmed.contains("Required")
192 || trimmed.contains("JsonProperty") || trimmed.contains("Obsolete"))
193 { csharp += 4; }
194 if trimmed.contains("string.") || trimmed.contains("String.")
195 && !trimmed.contains("import java.") { csharp += 2; }
196 if trimmed.starts_with("public class ") && !trimmed.contains("static void main")
197 { csharp += 2; }
198 if trimmed.contains("async Task") || trimmed.contains("Task<") { csharp += 5; }
199 if trimmed.contains("IEnumerable") || trimmed.contains("IList")
200 || trimmed.contains("IDictionary") { csharp += 5; }
201 if trimmed.starts_with("foreach ") { csharp += 5; }
202 if trimmed.contains("var ") && trimmed.contains("= new ") { csharp += 3; }
203 if trimmed.contains("get;") || trimmed.contains("set;") { csharp += 5; }
204 if trimmed.contains("=> ") && trimmed.contains(';')
205 && !trimmed.contains("===") { csharp += 2; }
206 if trimmed.starts_with("internal ") { csharp += 5; }
207 if trimmed.starts_with("sealed ") { csharp += 5; }
208 if trimmed.starts_with("partial class")
209 || trimmed.starts_with("partial struct") { csharp += 6; }
210 if trimmed.contains("nameof(") { csharp += 5; }
211 if trimmed.contains(".Select(") || trimmed.contains(".Where(")
212 || trimmed.contains(".OrderBy(") { csharp += 3; }
213 if trimmed.starts_with("record ") { csharp += 5; }
214
215 // Go.
216 if trimmed.starts_with("package ") { go += 6; }
217 if trimmed.starts_with("func ") { go += 5; }
218 if trimmed.contains(":= ") { go += 4; }
219 if trimmed.starts_with("import (") { go += 5; }
220 if trimmed.starts_with("defer ") { go += 5; }
221 if trimmed.contains("go func") { go += 5; }
222 if trimmed.contains("chan ") { go += 4; }
223 if trimmed.contains("fmt.") { go += 3; }
224 if trimmed.starts_with("type ") && trimmed.contains("struct {") { go += 5; }
225 if trimmed.starts_with("var ") { go += 1; }
226
227 // Java.
228 if trimmed.starts_with("public class ") { java += 6; }
229 if trimmed.starts_with("private ")
230 || trimmed.starts_with("protected ") { java += 3; }
231 if trimmed.contains("System.out") { java += 5; }
232 if trimmed.starts_with("import java.")
233 || trimmed.starts_with("import javax.") { java += 6; }
234 if trimmed.starts_with("@Override")
235 || trimmed.starts_with("@Deprecated") { java += 4; }
236 if trimmed.contains("public static void main") { java += 6; }
237 if trimmed.contains("throws ") { java += 3; }
238 if trimmed.starts_with("package ") && trimmed.contains(';') { java += 4; }
239
240 // JavaScript / TypeScript.
241 if trimmed.starts_with("const ") && trimmed.contains("= ") { js += 2; }
242 if trimmed.starts_with("function ") { js += 3; }
243 if trimmed.contains("=> {") || trimmed.contains("=> (") { js += 4; }
244 if trimmed.contains("===") || trimmed.contains("!==") { js += 5; }
245 if trimmed.contains("require(") { js += 5; }
246 if trimmed.contains("console.") { js += 4; }
247 if trimmed.starts_with("export ") { js += 3; }
248 if trimmed.starts_with("import ") && trimmed.contains("from ") { js += 5; }
249 if trimmed.starts_with("interface ") { js += 3; }
250 if trimmed.contains("document.") || trimmed.contains("window.") { js += 4; }
251 if trimmed.starts_with("async function") { js += 4; }
252
253 // Python.
254 if trimmed.starts_with("def ") { python += 4; }
255 if trimmed.starts_with("class ") && trimmed.ends_with(':') { python += 5; }
256 if trimmed.contains("self.") { python += 4; }
257 if trimmed.starts_with("elif ") { python += 6; }
258 if trimmed.starts_with("from ") && trimmed.contains("import ") { python += 5; }
259 if trimmed.starts_with("import ") && !trimmed.contains('{')
260 && !trimmed.contains("java.") && !trimmed.contains("from ") { python += 2; }
261 if trimmed.starts_with("# ") && !trimmed.starts_with("#include")
262 && !trimmed.starts_with("#define") && !trimmed.starts_with("#[") { python += 1; }
263 if trimmed.contains("__init__") || trimmed.contains("__main__") { python += 6; }
264 if trimmed.contains("None") && !trimmed.starts_with("//") { python += 1; }
265 if trimmed.starts_with("@") && !trimmed.starts_with("@Override")
266 && !trimmed.starts_with("@Deprecated") { python += 1; }
267 if (trimmed.starts_with("if ") || trimmed.starts_with("for ")
268 || trimmed.starts_with("while ")) && trimmed.ends_with(':') { python += 2; }
269 }
270
271 let scores = [
272 (rust, "rust"),
273 (c, "c"),
274 (cpp, "cpp"),
275 (csharp, "csharp"),
276 (go, "go"),
277 (java, "java"),
278 (js, "js"),
279 (python, "python"),
280 ];
281
282 let (best_score, best_lang) = scores.iter()
283 .fold((0, ""), |(bs, bl), &(s, l)| if s > bs { (s, l) } else { (bs, bl) });
284
285 // Require a minimum confidence.
286 if best_score >= 5 {
287 Some(best_lang)
288 } else {
289 None
290 }
291}
292
293/// Look up the `LangTokens` for a language name.
294fn lang_tokens_for(lang: &str) -> Outcome<lex::LangTokens> {
295 match lang {
296 "c" => Ok(lex::c_tokens()),
297 "cpp" | "c++" => Ok(lex::cpp_tokens()),
298 "csharp" | "cs" | "c#" => Ok(lex::csharp_tokens()),
299 "go" => Ok(lex::go_tokens()),
300 "java" => Ok(lex::java_tokens()),
301 "js" | "javascript" | "typescript" | "ts" => Ok(lex::js_tokens()),
302 "python" | "py" => Ok(lex::python_tokens()),
303 _ => Err(err!(
304 "Unsupported language: '{}'. Supported: rust, c, cpp, csharp, go, java, js, python",
305 lang;
306 Invalid, Input
307 )),
308 }
309}