1use crate::{Diagnostic, FileMode, ParsedFile, TokenKind, lex, parse_lexed, parse_with_mode};
2
3pub fn parse_bytes(source: Vec<u8>) -> Result<ByteParsedFile, Diagnostic> {
7 parse_bytes_with_mode(source, FileMode::Model)
8}
9
10pub fn parse_bytes_with_mode(
13 source: Vec<u8>,
14 mode: FileMode,
15) -> Result<ByteParsedFile, Diagnostic> {
16 let source = match String::from_utf8(source) {
17 Ok(source) => {
18 return Ok(ByteParsedFile {
19 original_bytes: None,
20 analysis_file: parse_with_mode(source, mode),
21 });
22 }
23 Err(error) => error.into_bytes(),
24 };
25 let mut analysis_bytes = source.clone();
26 let mut invalid_ranges = Vec::new();
27 let mut position = 0;
28 while let Err(error) = std::str::from_utf8(&source[position..]) {
29 let start = position + error.valid_up_to();
30 let end = start + error.error_len().unwrap_or(source.len() - start);
31 analysis_bytes[start..end].fill(b'?');
34 invalid_ranges.push(start..end);
35 position = end;
36 }
37 let analysis_source =
38 String::from_utf8(analysis_bytes).expect("invalid UTF-8 was replaced with ASCII");
39 let lexed = lex(analysis_source);
40 let mut token_index = 0;
41 for range in invalid_ranges {
42 while lexed.tokens()[token_index].range.end <= range.start {
43 token_index += 1;
44 }
45 let token = &lexed.tokens()[token_index];
46 if !matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
47 || range.end > token.range.end
48 {
49 return Err(Diagnostic {
50 range,
51 message: "invalid UTF-8 is only supported inside comments".into(),
52 });
53 }
54 }
55 Ok(ByteParsedFile {
56 original_bytes: Some(source),
57 analysis_file: parse_lexed(lexed, mode),
58 })
59}
60
61#[derive(Debug)]
63pub struct ByteParsedFile {
64 original_bytes: Option<Vec<u8>>,
65 analysis_file: ParsedFile,
66}
67
68impl ByteParsedFile {
69 pub fn into_parts(self) -> (ParsedFile, Option<Vec<u8>>) {
72 (self.analysis_file, self.original_bytes)
73 }
74
75 pub fn source_bytes(&self) -> &[u8] {
77 self.original_bytes
78 .as_deref()
79 .unwrap_or_else(|| self.analysis_file.source().as_bytes())
80 }
81
82 pub fn token_bytes(&self, index: usize) -> &[u8] {
85 &self.source_bytes()[self.analysis_file.tokens()[index].range.clone()]
86 }
87
88 pub fn analysis_file(&self) -> &ParsedFile {
93 &self.analysis_file
94 }
95
96 pub fn line_column(&self, byte_offset: usize) -> (usize, usize) {
100 self.analysis_file.line_column(byte_offset)
101 }
102}
103
104pub fn is_utf8_boundary(source: &[u8], offset: usize) -> bool {
107 if offset > source.len() {
108 return false;
109 }
110 for start in offset.saturating_sub(3)..offset {
111 let width = match source[start] {
112 0xc2..=0xdf => 2,
113 0xe0..=0xef => 3,
114 0xf0..=0xf4 => 4,
115 _ => continue,
116 };
117 let end = start + width;
118 if offset < end && end <= source.len() && std::str::from_utf8(&source[start..end]).is_ok() {
119 return false;
120 }
121 }
122 true
123}
124
125pub fn byte_line_column(source: &[u8], byte_offset: usize) -> (usize, usize) {
129 assert!(
130 is_utf8_boundary(source, byte_offset),
131 "invalid byte boundary"
132 );
133 let mut prefix = &source[..byte_offset];
134 let mut line = 1;
135 let mut column = 1;
136 let mut after_cr = false;
137 loop {
138 let (text, invalid_bytes) = match std::str::from_utf8(prefix) {
139 Ok(text) => (text, 0),
140 Err(error) => (
141 std::str::from_utf8(&prefix[..error.valid_up_to()]).unwrap(),
142 error
143 .error_len()
144 .unwrap_or(prefix.len() - error.valid_up_to()),
145 ),
146 };
147 for character in text.chars() {
148 match character {
149 '\r' => {
150 line += 1;
151 column = 1;
152 }
153 '\n' if after_cr => {}
154 '\n' => {
155 line += 1;
156 column = 1;
157 }
158 _ => column += 1,
159 }
160 after_cr = character == '\r';
161 }
162 if invalid_bytes == 0 {
163 return (line, column);
164 }
165 column += invalid_bytes;
166 after_cr = false;
167 prefix = &prefix[text.len() + invalid_bytes..];
168 }
169}