1use {
2 crate::diff::*,
3 std::{path::PathBuf, str::FromStr},
4};
5
6pub struct Parser {}
7#[derive(Debug)]
8enum ParserState {
9 Init,
10 Command,
11 Index,
12 OriginPath,
13 NewPath,
14 Hunk,
15 LineChange(Change),
16}
17
18#[derive(Debug)]
19pub struct ParseError {
20 kind: ParseErrorKind,
21 reason: String,
22 line: String,
23}
24#[derive(Debug)]
25pub enum ParseErrorKind {
26 InvalidLineStart,
27 ExpectationFailed,
28 InvalidLine,
29}
30
31impl Parser {
32 fn parse_line_kind(state: &ParserState, line: &str) -> Line {
33 match state {
34 ParserState::Init => {
35 if line.starts_with("diff") {
36 Line::Command
37 } else {
38 Line::Unknown
39 }
40 }
41 ParserState::Command => {
42 if line.starts_with("index") {
43 Line::Index
44 } else if line.starts_with(DIFF_SIGN_HEADER_ORIGIN) {
45 Line::OrignPath
46 } else {
47 Line::Unknown
48 }
49 }
50 ParserState::Index => {
51 if line.starts_with(DIFF_SIGN_HEADER_ORIGIN) {
52 Line::OrignPath
53 } else {
54 Line::Unknown
55 }
56 }
57 ParserState::OriginPath => {
58 if line.starts_with(DIFF_SIGN_HEADER_NEW) {
59 Line::NewPath
60 } else {
61 Line::Unknown
62 }
63 }
64 ParserState::NewPath => {
65 if line.starts_with(DIFF_SIGN_HUNK) {
66 Line::Hunk
67 } else {
68 Line::Unknown
69 }
70 }
71 ParserState::Hunk => match line.split_at(1) {
72 (DIFF_SIGN_LINE_ADDED, _) => Line::LineChange(Change::Added),
73 (DIFF_SIGN_LINE_DEFAULT, _) => {
74 Line::LineChange(Change::Default)
75 }
76 (DIFF_SIGN_LINE_DELETED, _) => {
77 Line::LineChange(Change::Deleted)
78 }
79 _ => Line::Unknown,
80 },
81 ParserState::LineChange(_) => {
82 if line.starts_with("diff") {
83 Line::Command
84 } else if line.starts_with("index") {
85 Line::Index
86 } else if line.starts_with(DIFF_SIGN_HEADER_ORIGIN) {
87 Line::OrignPath
88 } else if line.starts_with(DIFF_SIGN_HUNK) {
89 Line::Hunk
90 } else {
91 match line.split_at(1) {
92 (DIFF_SIGN_LINE_ADDED, _) => {
93 Line::LineChange(Change::Added)
94 }
95 (DIFF_SIGN_LINE_DEFAULT, _) => {
96 Line::LineChange(Change::Default)
97 }
98 (DIFF_SIGN_LINE_DELETED, _) => {
99 Line::LineChange(Change::Deleted)
100 }
101 _ => Line::Unknown,
102 }
103 }
104 }
105 }
106 }
107
108 fn parse_line_content<'line>(
109 line: &'line str,
110 kind: &Line,
111 ) -> Result<&'line str, ParseError> {
112 let content = match kind {
113 Line::Command => line,
114 Line::Index => {
115 line.strip_prefix("index ").ok_or_else(|| ParseError {
116 kind: ParseErrorKind::ExpectationFailed,
117 reason: "expect line start with `index `".to_string(),
118 line: line.to_string(),
119 })?
120 }
121 Line::OrignPath => {
122 line.strip_prefix("--- ").ok_or_else(|| ParseError {
123 kind: ParseErrorKind::ExpectationFailed,
124 reason: "expect line start with `--- `".to_string(),
125 line: line.to_string(),
126 })?
127 }
128 Line::NewPath => {
129 line.strip_prefix("+++ ").ok_or_else(|| ParseError {
130 kind: ParseErrorKind::ExpectationFailed,
131 reason: "expect line start with `+++ `".to_string(),
132 line: line.to_string(),
133 })?
134 }
135 Line::Hunk => {
136 let end_offset =
137 line.find(" @@").ok_or_else(|| ParseError {
138 kind: ParseErrorKind::ExpectationFailed,
139 reason: "cannot find hunk end with ` @@`".to_string(),
140 line: line.to_string(),
141 })?;
142 line.split_at(end_offset).0.strip_prefix("@@ ").ok_or_else(
143 || ParseError {
144 kind: ParseErrorKind::ExpectationFailed,
145 reason: "expect line start with `@@ `".to_string(),
146 line: line.to_string(),
147 },
148 )?
149 }
150 Line::LineChange(_) => line.split_at(1).1,
151 Line::Unknown => panic!("unknown line start"),
153 };
154
155 Ok(content)
156 }
157
158 pub fn parse_git_udiff(src: &str) -> Result<DiffComposition, ParseError> {
159 let mut state = ParserState::Init;
160 let mut diffcom = DiffComposition {
171 format: DiffFormat::GitUdiff,
172 diff: Vec::new(),
173 };
174
175 let mut diff_cur: Option<Diff> = None;
176 let mut hunk_cur: Option<DiffHunk> = None;
177
178 for line in src.lines() {
179 let tag = Self::parse_line_kind(&state, line);
180 state = match &tag {
181 Line::Command => ParserState::Command,
182 Line::Index => ParserState::Index,
183 Line::OrignPath => ParserState::OriginPath,
184 Line::NewPath => ParserState::NewPath,
185 Line::Hunk => ParserState::Hunk,
186 Line::LineChange(change_kind) => {
187 ParserState::LineChange(*change_kind)
188 }
189 Line::Unknown => Err(ParseError {
190 kind: ParseErrorKind::InvalidLineStart,
191 reason: "line starting with invalid token".to_string(),
192 line: line.to_string(),
193 })?,
194 };
195 let content = Self::parse_line_content(line, &tag)?;
196 match state {
197 ParserState::Init => unreachable!(),
198 ParserState::Command => {
199 if diff_cur.is_some() {
200 let mut diff_before = diff_cur.take().unwrap();
201
202 if hunk_cur.is_some() {
203 let hunk_before = hunk_cur.take().unwrap();
204 diff_before.hunk.push(hunk_before);
205 }
206 diffcom.diff.push(diff_before);
207 }
208 let (file_path_a, file_path_b) = content
209 .strip_prefix("diff --git ")
210 .and_then(|s|s.split_once(' '))
211 .ok_or_else(||{
212 ParseError{
213 kind:ParseErrorKind::ExpectationFailed,
214 reason:"lines not starting with `diff --git ` or cannot split command's arguments".to_string(
215 ),
216 line:line.to_string(),
217 }
218 })?;
219 let file_path_a = file_path_a
220 .strip_prefix("a/")
221 .ok_or_else(|| ParseError {
222 kind: ParseErrorKind::ExpectationFailed,
223 reason: "expect to path_a start with `a/`"
224 .to_string(),
225 line: line.to_string(),
226 })?;
227
228 let file_path_b = file_path_b
229 .strip_prefix("b/")
230 .ok_or_else(|| ParseError {
231 kind: ParseErrorKind::ExpectationFailed,
232 reason: "expect to path_b start with `b/`"
233 .to_string(),
234 line: line.to_string(),
235 })?;
236 if file_path_a != file_path_b {
237 Err(ParseError {
238 kind: ParseErrorKind::ExpectationFailed,
239 reason: "file path a and b are different"
240 .to_string(),
241 line: line.to_string(),
242 })?;
243 }
244 let path = PathBuf::from_str(file_path_a).map_err(|e| {
245 ParseError {
246 kind: ParseErrorKind::ExpectationFailed,
247 reason: format!("cannot parse file_path, {:?}", e),
248 line: line.to_string(),
249 }
250 })?;
251
252 diff_cur = Some(Diff {
253 path,
254 hunk: Vec::new(),
255 command: Some(content.to_string()),
256 index: None,
257 });
258 }
259 ParserState::Index => match &mut diff_cur {
260 Some(cur) => {
261 if cur.index.is_some() {
262 Err(ParseError {
263 kind: ParseErrorKind::InvalidLine,
264 reason: format!(
265 "there is index in current diff {:?}",
266 &diff_cur
267 ),
268 line: line.to_string(),
269 })?;
270 } else {
271 cur.index = Some(content.to_string())
272 }
273 }
274 None => {
275 Err(ParseError {
276 kind: ParseErrorKind::ExpectationFailed,
277 reason: format!(
278 "there is no current diff {:?}",
279 &diff_cur
280 ),
281 line: line.to_string(),
282 })?;
283 }
284 },
285 ParserState::OriginPath => match &diff_cur {
286 Some(d) => {
287 let diff_path =
288 d.path.to_str().ok_or_else(|| ParseError {
289 kind: ParseErrorKind::ExpectationFailed,
290 reason: "cannot convert diff path to str"
291 .to_string(),
292 line: line.to_string(),
293 })?;
294
295 let origin_path = content
296 .strip_prefix("a/")
297 .ok_or_else(|| ParseError {
298 kind: ParseErrorKind::ExpectationFailed,
299 reason: "old file path not start with `a/`"
300 .to_string(),
301 line: line.to_string(),
302 })?;
303 if diff_path != origin_path {
304 Err(ParseError {
305 kind: ParseErrorKind::ExpectationFailed,
306 reason: format!(
307 "diff path and origin path is different, [diff: {}] [origin: {}]",
308 diff_path, origin_path
309 ),
310 line: line.to_string(),
311 })?;
312 }
313 }
314 None => {
315 Err(ParseError {
316 kind: ParseErrorKind::ExpectationFailed,
317 reason: format!(
318 "there is no current diff {:?}",
319 &diff_cur
320 ),
321 line: line.to_string(),
322 })?;
323 }
324 },
325 ParserState::NewPath => match &diff_cur {
326 Some(d) => {
327 let diff_path =
328 d.path.to_str().ok_or_else(|| ParseError {
329 kind: ParseErrorKind::ExpectationFailed,
330 reason: "cannot convert diff path to str"
331 .to_string(),
332 line: line.to_string(),
333 })?;
334
335 let new_path =
336 content.strip_prefix("b/").ok_or_else(|| {
337 ParseError {
338 kind: ParseErrorKind::ExpectationFailed,
339 reason: "old file path not start with `b/`"
340 .to_string(),
341 line: line.to_string(),
342 }
343 })?;
344 if diff_path != new_path {
345 Err(ParseError {
346 kind: ParseErrorKind::ExpectationFailed,
347 reason: format!(
348 "diff path and new path is different, [diff: {}] [new: {}]",
349 diff_path, new_path
350 ),
351 line: line.to_string(),
352 })?;
353 }
354 }
355 None => {
356 Err(ParseError {
357 kind: ParseErrorKind::ExpectationFailed,
358 reason: format!(
359 "there is no current diff {:?}",
360 &diff_cur
361 ),
362 line: line.to_string(),
363 })?;
364 }
365 },
366 ParserState::Hunk => match &mut diff_cur {
367 Some(dc) => {
368 if let Some(hunk_before) = hunk_cur.take() {
369 dc.hunk.push(hunk_before)
370 }
371 let (old, new) =
372 content.split_once(' ').ok_or_else(|| {
373 ParseError {
374 kind: ParseErrorKind::ExpectationFailed,
375 reason: "there is no space in hunk line"
376 .to_string(),
377 line: line.to_string(),
378 }
379 })?;
380 let (old_line, old_len) =
381 old.split_once(',').ok_or_else(|| ParseError {
382 kind: ParseErrorKind::ExpectationFailed,
383 reason: "cannot split hunk old range with `,`"
384 .to_string(),
385 line: line.to_string(),
386 })?;
387 let (new_line, new_len) =
388 new.split_once(',').ok_or_else(|| ParseError {
389 kind: ParseErrorKind::ExpectationFailed,
390 reason: "cannot split hunk new range with `,`"
391 .to_string(),
392 line: line.to_string(),
393 })?;
394
395 let old_line = old_line
396 .strip_prefix('-')
397 .ok_or_else(|| ParseError {
398 kind: ParseErrorKind::ExpectationFailed,
399 reason: "cannot strip `-` of old_line"
400 .to_string(),
401 line: line.to_string(),
402 })?
403 .parse::<usize>()
404 .map_err(|e| ParseError {
405 kind: ParseErrorKind::ExpectationFailed,
406 reason: format!(
407 "cannot parse old_line to usize, {:?}",
408 e
409 ),
410 line: line.to_string(),
411 })?;
412 let old_len =
413 old_len.parse::<usize>().map_err(|e| {
414 ParseError {
415 kind: ParseErrorKind::ExpectationFailed,
416 reason: format!(
417 "cannot parse old_len to usize, {:?}",
418 e
419 ),
420 line: line.to_string(),
421 }
422 })?;
423 let new_line = new_line
424 .strip_prefix('+')
425 .ok_or_else(|| ParseError {
426 kind: ParseErrorKind::ExpectationFailed,
427 reason: "cannot strip `+` of new_line"
428 .to_string(),
429 line: line.to_string(),
430 })?
431 .parse::<usize>()
432 .map_err(|e| ParseError {
433 kind: ParseErrorKind::ExpectationFailed,
434 reason: format!(
435 "cannot parse new_line to usize, {:?}",
436 e
437 ),
438 line: line.to_string(),
439 })?;
440 let new_len =
441 new_len.parse::<usize>().map_err(|e| {
442 ParseError {
443 kind: ParseErrorKind::ExpectationFailed,
444 reason: format!(
445 "cannot parse new_len to usize, {:?}",
446 e
447 ),
448 line: line.to_string(),
449 }
450 })?;
451
452 hunk_cur = Some(DiffHunk {
453 old_line,
454 old_len,
455 new_line,
456 new_len,
457 change: Vec::new(),
458 });
459 }
460 None => {
461 panic!("there is no current diff {:?}", &diff_cur)
462 }
463 },
464 ParserState::LineChange(kind) => match &mut hunk_cur {
465 Some(h) => {
466 let change = LineChange {
467 kind,
468 content: content.to_string(),
469 };
470 h.change.push(change)
471 }
472 None => {
473 Err(ParseError {
474 kind: ParseErrorKind::ExpectationFailed,
475 reason: format!(
476 "there is no current hunk. current diff {:?}",
477 &diff_cur
478 ),
479 line: line.to_string(),
480 })?;
481 }
482 },
483 }
484 }
485
486 if let Some(hunk) = hunk_cur {
487 match &mut diff_cur {
488 Some(c) => c.hunk.push(hunk),
489 None => {
490 Err(ParseError {
491 kind: ParseErrorKind::ExpectationFailed,
492 reason: "there is no diff_cur to add hunk".to_string(),
493 line: "".to_string(),
494 })?;
495 }
496 }
497 }
498 if let Some(diff) = diff_cur {
499 diffcom.diff.push(diff);
500 } else {
501 Err(ParseError {
502 kind: ParseErrorKind::ExpectationFailed,
503 reason: "there is no diff_cur at end".to_string(),
504 line: "".to_string(),
505 })?;
506 }
507
508 Ok(diffcom)
509 }
510}
511
512#[cfg(test)]
513mod test {
514 use core::panic;
515
516 use crate::{
517 diff::*,
518 parser::{Parser, ParserState},
519 };
520
521 const short_test_data: &str = r#"diff --git a/tests/vm.rs b/tests/vm.rs
522index 90d5af1..30044cb 100644
523--- a/tests/vm.rs
524+++ b/tests/vm.rs
525@@ -16,7 +16,9 @@ fn run_vm_test(tests: Tests<Option<Object>>) {
526 let program = Parser::new(lexer).parse().unwrap();
527
528 let mut comp = Compiler::create().unwrap();
529- comp.compile(program);
530+ if let Err(e) = comp.compile(program) {
531+ panic!("Compile error {:?}", e);
532+ }
533 let bytecode = comp.bytecode().unwrap();
534
535 println!("Bytecode\n{}", bytecode.to_string());
536@@ -25,7 +27,7 @@ fn run_vm_test(tests: Tests<Option<Object>>) {
537
538 while vm.is_runable() {
539 if let Err(err) = vm.run_single() {
540- eprintln!("Error {:?}", err);
541+ panic!("VmError {:?}", err)
542 }
543 }
544 println!("VM STACK:\n {}", vm.stack_to_string());
545@@ -262,8 +264,7 @@ let no_return = fn() { };no_return() no_return() no_return() no_return()
546
547 tests.add((
548 "
549-let fun = fn() { 10 + 20 };
550-fun()
551+let fun = fn() { 10 + 20 }; fun()
552 ",
553 Some(Object::Int(Int { value: 30 })),
554 ));
555"#;
556 #[test]
557 fn test_parse_linestart() {
558 let mut state = ParserState::Init;
559 for line in short_test_data.lines() {
560 let tag = Parser::parse_line_kind(&state, line);
561 state = match &tag {
562 Line::Command => ParserState::Command,
563 Line::Index => ParserState::Index,
564 Line::OrignPath => ParserState::OriginPath,
565 Line::NewPath => ParserState::NewPath,
566 Line::Hunk => ParserState::Hunk,
567 Line::LineChange(change_kind) => {
568 ParserState::LineChange(*change_kind)
569 }
570 Line::Unknown => panic!("Unknown line start"),
571 };
572
573 println!("[S:{:?}] [T:{:?}] -- L>{}", &state, &tag, &line);
574 }
575 }
576
577 #[test]
578 fn test_parse_udiff() {
579 let com = Parser::parse_git_udiff(short_test_data).unwrap();
580 println!("{:#?}", com);
581 }
582}