Skip to content

Commit 0749814

Browse files
authored
Update main.rs
1 parent eaea0d8 commit 0749814

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

source-code/hsdf/src/main.rs

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,284 @@
1+
use std::fs::File;
2+
use std::io::{self, Read};
3+
use std::path::PathBuf;
14

5+
use clap::Parser;
6+
use miette::{Diagnostic, GraphicalReportHandler, Report, SourceSpan};
7+
use regex::Regex;
8+
9+
#[derive(Parser, Debug)]
10+
#[command(name = "hsdf", about = "HackerScript Diagnostic Files - Diagnoses .hcs files with pretty errors")]
11+
struct Args {
12+
/// Path to the .hcs file to diagnose
13+
#[arg(required = true)]
14+
file: PathBuf,
15+
}
16+
17+
#[derive(Debug, thiserror::Error, Diagnostic)]
18+
enum HcsError {
19+
#[error("File not found or unable to read: {0}")]
20+
IoError(#[from] io::Error),
21+
22+
#[error("Unclosed block comment")]
23+
#[diagnostic(code(hcs::unclosed_block_comment))]
24+
UnclosedBlockComment {
25+
#[source_code]
26+
src: String,
27+
#[label("Block comment started here but never closed")]
28+
span: SourceSpan,
29+
},
30+
31+
#[error("Unmatched closing bracket ']' without opening '['")]
32+
#[diagnostic(code(hcs::unmatched_closing_bracket))]
33+
UnmatchedClosingBracket {
34+
#[source_code]
35+
src: String,
36+
#[label("This ']' has no matching '['")]
37+
span: SourceSpan,
38+
},
39+
40+
#[error("Unclosed sh block")]
41+
#[diagnostic(code(hcs::unclosed_sh_block))]
42+
UnclosedShBlock {
43+
#[source_code]
44+
src: String,
45+
#[label("sh block started here but never closed")]
46+
span: SourceSpan,
47+
},
48+
49+
#[error("Unclosed block (indent level > 0 at EOF)")]
50+
#[diagnostic(code(hcs::unclosed_block))]
51+
UnclosedBlock {
52+
#[source_code]
53+
src: String,
54+
#[label("Block opened here but not closed")]
55+
span: SourceSpan,
56+
},
57+
58+
#[error("Invalid syntax: {message}")]
59+
#[diagnostic(code(hcs::invalid_syntax))]
60+
InvalidSyntax {
61+
message: String,
62+
#[source_code]
63+
src: String,
64+
#[label("Invalid syntax here")]
65+
span: SourceSpan,
66+
},
67+
68+
#[error("Multiple errors found")]
69+
MultipleErrors(Vec<Report>),
70+
}
71+
72+
fn main() -> miette::Result<()> {
73+
let args = Args::parse();
74+
75+
let mut file = File::open(&args.file)?;
76+
let mut code = String::new();
77+
file.read_to_string(&mut code)?;
78+
79+
match diagnose_hcs(&code) {
80+
Ok(_) => {
81+
println!("No errors found in {}", args.file.display());
82+
Ok(())
83+
}
84+
Err(HcsError::MultipleErrors(errors)) => {
85+
let handler = GraphicalReportHandler::new();
86+
for err in errors {
87+
handler.render_report(&mut io::stdout(), err.as_ref())?;
88+
}
89+
std::process::exit(1);
90+
}
91+
Err(err) => {
92+
let report = Report::new(err);
93+
let handler = GraphicalReportHandler::new();
94+
handler.render_report(&mut io::stdout(), report.as_ref())?;
95+
std::process::exit(1);
96+
}
97+
}
98+
}
99+
100+
fn diagnose_hcs(code: &str) -> Result<(), HcsError> {
101+
let lines: Vec<&str> = code.lines().collect();
102+
let mut errors: Vec<Report> = Vec::new();
103+
104+
let mut indent_level = 0;
105+
let mut in_sh_block = false;
106+
let mut in_block_comment = false;
107+
let mut block_comment_start: Option<usize> = None;
108+
let mut sh_block_start: Option<usize> = None;
109+
let mut last_open_block_pos: Option<usize> = None;
110+
111+
let rust_re = Regex::new(r"<rust:([\w\-]+)(?:=([\d\.]+))?>").unwrap();
112+
let c_re = Regex::new(r"<c:(.*)>").unwrap();
113+
let comment_re = Regex::new(r"@.*").unwrap();
114+
115+
let mut pos = 0;
116+
for (line_num, line) in lines.iter().enumerate() {
117+
let line_start = pos;
118+
let mut raw_line = line.trim().to_string();
119+
120+
// Advance pos
121+
pos += line.len() + 1; // +1 for newline
122+
123+
// Handle block comments
124+
if raw_line.contains("-/") {
125+
if in_block_comment {
126+
errors.push(Report::new(HcsError::InvalidSyntax {
127+
message: "Nested block comment start".to_string(),
128+
src: code.to_string(),
129+
span: (line_start, raw_line.len()).into(),
130+
}));
131+
}
132+
in_block_comment = true;
133+
block_comment_start = Some(line_start);
134+
continue;
135+
}
136+
if raw_line.contains("-\\") {
137+
if !in_block_comment {
138+
errors.push(Report::new(HcsError::UnmatchedClosingBracket {
139+
src: code.to_string(),
140+
span: (line_start, raw_line.len()).into(),
141+
}));
142+
}
143+
in_block_comment = false;
144+
block_comment_start = None;
145+
continue;
146+
}
147+
if in_block_comment {
148+
continue;
149+
}
150+
151+
// Remove line comments
152+
raw_line = comment_re.replace(&raw_line, "").trim().to_string();
153+
154+
if raw_line.is_empty() && !in_sh_block {
155+
continue;
156+
}
157+
158+
// Special imports
159+
if rust_re.is_match(&raw_line) {
160+
// Valid, skip
161+
continue;
162+
}
163+
if c_re.is_match(&raw_line) {
164+
// Valid, skip
165+
continue;
166+
}
167+
168+
// Manual mode
169+
if raw_line.contains("--- manual ---") {
170+
// Valid
171+
continue;
172+
}
173+
174+
// Numpy syntax
175+
if raw_line.starts_with("matrix ") || raw_line.starts_with("vector ") {
176+
// Assume valid for now
177+
continue;
178+
}
179+
180+
// SH commands
181+
if raw_line == "sh [" {
182+
if in_sh_block {
183+
errors.push(Report::new(HcsError::InvalidSyntax {
184+
message: "Nested sh block".to_string(),
185+
src: code.to_string(),
186+
span: (line_start, raw_line.len()).into(),
187+
}));
188+
}
189+
in_sh_block = true;
190+
sh_block_start = Some(line_start);
191+
continue;
192+
}
193+
if in_sh_block {
194+
if raw_line == "]" {
195+
in_sh_block = false;
196+
sh_block_start = None;
197+
continue;
198+
}
199+
// Otherwise, sh content, assume valid
200+
continue;
201+
}
202+
if raw_line.starts_with("sh [") && raw_line.ends_with("]") {
203+
// Single line sh, valid
204+
continue;
205+
}
206+
207+
// Block handling
208+
if raw_line.starts_with("] except") || raw_line.starts_with("] else") {
209+
if indent_level == 0 {
210+
errors.push(Report::new(HcsError::UnmatchedClosingBracket {
211+
src: code.to_string(),
212+
span: (line_start, raw_line.len()).into(),
213+
}));
214+
} else {
215+
indent_level -= 1;
216+
}
217+
continue;
218+
}
219+
if raw_line == "]" {
220+
if indent_level == 0 {
221+
errors.push(Report::new(HcsError::UnmatchedClosingBracket {
222+
src: code.to_string(),
223+
span: (line_start, raw_line.len()).into(),
224+
}));
225+
} else {
226+
indent_level -= 1;
227+
}
228+
continue;
229+
}
230+
231+
// Keywords
232+
if raw_line.starts_with("func ") || raw_line.starts_with("log ") {
233+
// Valid
234+
}
235+
236+
// Opening blocks
237+
if raw_line.ends_with("[") {
238+
indent_level += 1;
239+
last_open_block_pos = Some(line_start);
240+
continue;
241+
}
242+
243+
// If we reach here and it's not recognized, flag as invalid
244+
if !raw_line.is_empty() {
245+
errors.push(Report::new(HcsError::InvalidSyntax {
246+
message: "Unrecognized syntax".to_string(),
247+
src: code.to_string(),
248+
span: (line_start, raw_line.len()).into(),
249+
}));
250+
}
251+
}
252+
253+
// Check for unclosed states
254+
if in_block_comment {
255+
if let Some(start) = block_comment_start {
256+
errors.push(Report::new(HcsError::UnclosedBlockComment {
257+
src: code.to_string(),
258+
span: (start, 2).into(), // Approximate span for "-/"
259+
}));
260+
}
261+
}
262+
if in_sh_block {
263+
if let Some(start) = sh_block_start {
264+
errors.push(Report::new(HcsError::UnclosedShBlock {
265+
src: code.to_string(),
266+
span: (start, 4).into(), // "sh ["
267+
}));
268+
}
269+
}
270+
if indent_level > 0 {
271+
if let Some(start) = last_open_block_pos {
272+
errors.push(Report::new(HcsError::UnclosedBlock {
273+
src: code.to_string(),
274+
span: (start, 1).into(), // "["
275+
}));
276+
}
277+
}
278+
279+
if !errors.is_empty() {
280+
Err(HcsError::MultipleErrors(errors))
281+
} else {
282+
Ok(())
283+
}
284+
}

0 commit comments

Comments
 (0)