From 206151222726354d7f1e6c6d93265b0fdd9fe5d7 Mon Sep 17 00:00:00 2001 From: Krusty/Benediction Date: Sat, 23 Nov 2024 16:35:30 +0100 Subject: [PATCH] [bndbuild] Add snapsjot command to the list --- Cargo.lock | 2 + cpclib-asm/src/assembler/file.rs | 47 +-- cpclib-asm/src/assembler/mod.rs | 15 +- cpclib-asm/src/assembler/processed_token.rs | 3 +- cpclib-asm/src/parser/parser.rs | 290 +++++++++++------- cpclib-bndbuild/Cargo.toml | 22 +- cpclib-bndbuild/src/executor.rs | 2 + cpclib-bndbuild/src/runners/disc.rs | 2 - cpclib-bndbuild/src/runners/mod.rs | 1 + cpclib-bndbuild/src/runners/snapshot.rs | 51 +++ cpclib-bndbuild/src/task.rs | 37 ++- cpclib-bndbuild/tests/delegated/build.bnd | 2 +- cpclib-disc/exec/catalog.rs | 5 +- cpclib-runner/src/runner/emulator/sugarbox.rs | 15 +- cpclib-sna/Cargo.toml | 13 +- cpclib-sna/src/bin/snapshot.rs | 278 +---------------- cpclib-sna/src/cli.rs | 5 +- cpclib-sna/src/error.rs | 7 +- cpclib-sna/src/lib.rs | 285 ++++++++++++++++- 19 files changed, 610 insertions(+), 472 deletions(-) create mode 100644 cpclib-bndbuild/src/runners/snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index 69f5a4fe..091a67d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1802,6 +1802,7 @@ dependencies = [ "cpclib-disc", "cpclib-imgconverter", "cpclib-runner", + "cpclib-sna", "cpclib-xfertool", "dot-writer", "fancy-duration", @@ -1986,6 +1987,7 @@ dependencies = [ "comfy-table", "cpclib-common", "delegate", + "line-span", "minus", "num_enum", "nutype", diff --git a/cpclib-asm/src/assembler/file.rs b/cpclib-asm/src/assembler/file.rs index dad3e6c4..543bbf9a 100644 --- a/cpclib-asm/src/assembler/file.rs +++ b/cpclib-asm/src/assembler/file.rs @@ -46,17 +46,16 @@ impl<'a, 'b> From<(&'a str, &'b Env)> for Fname<'a, 'b> { } } - pub enum AnyFileNameOwned { InImage { image: String, content: String }, Standard(String) } -impl<'fname> From<&AnyFileName<'fname>> for AnyFileNameOwned { +impl<'fname> From<&AnyFileName<'fname>> for AnyFileNameOwned { fn from(value: &AnyFileName) -> Self { match value { AnyFileName::InImage { image, content } => Self::new_in_image(*image, *content), - AnyFileName::Standard(content) => Self::new_standard(*content), + AnyFileName::Standard(content) => Self::new_standard(*content) } } } @@ -64,7 +63,10 @@ impl<'fname> From<&AnyFileName<'fname>> for AnyFileNameOwned { impl<'fname> From<&'fname AnyFileNameOwned> for AnyFileName<'fname> { fn from(value: &'fname AnyFileNameOwned) -> Self { match value { - AnyFileNameOwned::InImage { image, content: amsdos } => { + AnyFileNameOwned::InImage { + image, + content: amsdos + } => { AnyFileName::InImage { image: image.as_str(), content: amsdos.as_str() @@ -82,7 +84,6 @@ impl From<&str> for AnyFileNameOwned { } } - impl AnyFileNameOwned { pub fn new_standard>(fname: S) -> Self { Self::Standard(fname.into()) @@ -100,7 +101,6 @@ impl AnyFileNameOwned { } } - /// Helper to handler filenames that contains both a dsk name and a file pub enum AnyFileName<'fname> { InImage { @@ -118,7 +118,10 @@ impl<'fname> AnyFileName<'fname> { } pub fn new_in_image(image: &'fname str, amsdos: &'fname str) -> Self { - Self::InImage { image, content: amsdos } + Self::InImage { + image, + content: amsdos + } } pub fn use_image(&self) -> bool { @@ -128,32 +131,38 @@ impl<'fname> AnyFileName<'fname> { } } - pub fn image_filename(&self) -> Option<&str> { match self { - AnyFileName::InImage { image, ..} => Some(image), - AnyFileName::Standard(_) => None, + AnyFileName::InImage { image, .. } => Some(image), + AnyFileName::Standard(_) => None } } pub fn content_filename(&self) -> &str { match self { - AnyFileName::InImage { image, content: amsdos } => amsdos, - AnyFileName::Standard(content) => content, + AnyFileName::InImage { + image, + content: amsdos + } => amsdos, + AnyFileName::Standard(content) => content } } pub fn basm_fname(&self) -> Cow { match self { - AnyFileName::InImage { image, content } => Cow::Owned(format!("{}{}{}", image, Self::DSK_SEPARATOR, content)), - AnyFileName::Standard(content) => Cow::Borrowed(content), + AnyFileName::InImage { image, content } => { + Cow::Owned(format!("{}{}{}", image, Self::DSK_SEPARATOR, content)) + }, + AnyFileName::Standard(content) => Cow::Borrowed(content) } } - fn base_filename(&self) -> &str { match self { - AnyFileName::InImage { image, content: amsdos } => image, + AnyFileName::InImage { + image, + content: amsdos + } => image, AnyFileName::Standard(f) => f } } @@ -216,7 +225,7 @@ impl<'fname> From<&'fname str> for AnyFileName<'fname> { "Need to handle case where fname as several {}", Self::DSK_SEPARATOR ) - }, + } } } } @@ -228,9 +237,7 @@ pub fn get_filename_to_read>( ) -> Result { let fname = fname.as_ref(); - AnyFileName::from(fname) - .path_for_base_filename(options, env) - + AnyFileName::from(fname).path_for_base_filename(options, env) } /// Load a file and remove header if any diff --git a/cpclib-asm/src/assembler/mod.rs b/cpclib-asm/src/assembler/mod.rs index f933111b..3f090687 100644 --- a/cpclib-asm/src/assembler/mod.rs +++ b/cpclib-asm/src/assembler/mod.rs @@ -2043,9 +2043,7 @@ impl Env { .int()? as u8; } if let Some(step) = step { - brk.step = Some(self - .resolve_expr_may_fail_in_first_pass(step)? - .int()? as _); + brk.step = Some(self.resolve_expr_may_fail_in_first_pass(step)?.int()? as _); } if let Some(condition) = condition { let cond = self.resolve_expr_may_fail_in_first_pass(condition)?; @@ -2907,16 +2905,13 @@ impl Env { let amsdos_fname = self.build_fname(amsdos_fname)?; let any_fname: AnyFileNameOwned = match dsk_fname { - Some(dsk_fname) => AnyFileNameOwned::new_in_image( - self.build_fname(dsk_fname)?, amsdos_fname - ), - None => { - AnyFileNameOwned::from(amsdos_fname.as_str()) - } + Some(dsk_fname) => { + AnyFileNameOwned::new_in_image(self.build_fname(dsk_fname)?, amsdos_fname) + }, + None => AnyFileNameOwned::from(amsdos_fname.as_str()) }; let any_fname = any_fname.as_any_filename(); - let (amsdos_fname, dsk_fname) = (any_fname.content_filename(), any_fname.image_filename()); let amsdos_fname = Utf8PathBuf::from(amsdos_fname); diff --git a/cpclib-asm/src/assembler/processed_token.rs b/cpclib-asm/src/assembler/processed_token.rs index f2887501..d032ccb3 100644 --- a/cpclib-asm/src/assembler/processed_token.rs +++ b/cpclib-asm/src/assembler/processed_token.rs @@ -979,7 +979,8 @@ where // Handle file loading let fname = self.token.incbin_fname(); let fname = env.build_fname(fname)?; - let fname = get_filename_to_read(fname, options.parse_options(), Some(env))?; + let fname = + get_filename_to_read(fname, options.parse_options(), Some(env))?; // get the data for the given file let data = if !contents.contains_key(&fname) { diff --git a/cpclib-asm/src/parser/parser.rs b/cpclib-asm/src/parser/parser.rs index 14020297..2514dd86 100644 --- a/cpclib-asm/src/parser/parser.rs +++ b/cpclib-asm/src/parser/parser.rs @@ -14,7 +14,6 @@ use cpclib_common::itertools::Itertools; use cpclib_common::rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use cpclib_common::smallvec::SmallVec; use cpclib_common::smol_str::SmolStr; -use cpclib_common::winnow::{self, Located, Stateful}; use cpclib_common::winnow::ascii::{alpha1, alphanumeric1, line_ending, space0, Caseless}; use cpclib_common::winnow::combinator::{ alt, cut_err, delimited, eof, not, opt, peek, preceded, repeat, separated, terminated @@ -24,7 +23,7 @@ use cpclib_common::winnow::stream::{ Accumulate, AsBStr, AsBytes, AsChar, Checkpoint, Offset, Stream, UpdateSlice }; use cpclib_common::winnow::token::{none_of, one_of, take, take_till, take_until, take_while}; -use cpclib_common::winnow::{BStr, PResult, Parser}; +use cpclib_common::winnow::{self, BStr, Located, PResult, Parser, Stateful}; use cpclib_sna::parse::parse_flag; use cpclib_sna::{ FlagValue, RemuBreakPointAccessMode, RemuBreakPointRunMode, RemuBreakPointType, SnapshotVersion @@ -2370,13 +2369,13 @@ pub fn parse_directive_new( let token: LocatedTokenInner = match word.len() { 2 => parse_directive_of_size_2(input, &input_start, is_orgams, within_struct, word), 3 => parse_directive_of_size3(input, &input_start, is_orgams, within_struct, word), - 4 => parse_directive_of_size_4(input, &input_start, is_orgams, within_struct, word), - 5 => parse_directive_of_size_5(input, &input_start, is_orgams, within_struct, word), - 6 => parse_directive_of_size_6(input, &input_start, is_orgams, within_struct, word), - 7 => parse_directive_of_size_7(input, &input_start, is_orgams, within_struct, word), - 8 => parse_directive_of_size_8(input, &input_start, is_orgams, within_struct, word), - 10 => parse_directive_of_size_10(input, &input_start, is_orgams, within_struct, word), - _ => parse_directive_of_size_others(input, &input_start, is_orgams, within_struct, word) + 4 => parse_directive_of_size_4(input, &input_start, is_orgams, within_struct, word), + 5 => parse_directive_of_size_5(input, &input_start, is_orgams, within_struct, word), + 6 => parse_directive_of_size_6(input, &input_start, is_orgams, within_struct, word), + 7 => parse_directive_of_size_7(input, &input_start, is_orgams, within_struct, word), + 8 => parse_directive_of_size_8(input, &input_start, is_orgams, within_struct, word), + 10 => parse_directive_of_size_10(input, &input_start, is_orgams, within_struct, word), + _ => parse_directive_of_size_others(input, &input_start, is_orgams, within_struct, word) }?; let token = token.into_located_token_between(&input_start, *input); @@ -2384,36 +2383,51 @@ pub fn parse_directive_new( } } -fn parse_directive_of_size_others(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { -match &word.to_ascii_uppercase()[..] { - //12 - #[cfg(not(target_arch = "wasm32"))] - b"INCSHRINKLER" => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::Shrinkler)) - .parse_next(input) - }, +fn parse_directive_of_size_others( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { + match &word.to_ascii_uppercase()[..] { + // 12 + #[cfg(not(target_arch = "wasm32"))] + b"INCSHRINKLER" => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::Shrinkler)).parse_next(input) + }, - // 13 - b"STARTINGINDEX" => parse_startingindex.parse_next(input), + // 13 + b"STARTINGINDEX" => parse_startingindex.parse_next(input), - _ => { - input.reset(input_start); - Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( - input, - ErrorKind::Alt - ))) + _ => { + input.reset(input_start); + Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( + input, + ErrorKind::Alt + ))) + } } - } -} - -fn parse_directive_of_size_10(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size_10( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { choice_nocase!(b"ASMCONTROL") => parse_assembler_control.parse_next(input), - choice_nocase!(b"BREAKPOINT") => parse_breakpoint.parse_next(input), - choice_nocase!(b"DEFSECTION") => parse_range.parse_next(input), - + choice_nocase!(b"BREAKPOINT") => parse_breakpoint.parse_next(input), + choice_nocase!(b"DEFSECTION") => parse_range.parse_next(input), + _ => { input.reset(input_start); Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( @@ -2421,21 +2435,27 @@ fn parse_directive_of_size_10(input: &mut InnerZ80Span, input_start: &Checkpoin ErrorKind::Alt ))) } - } } -fn parse_directive_of_size_8(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size_8( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { - choice_nocase!(b"BINCLUDE") => { - parse_incbin(BinaryTransformation::None).parse_next(input) - }, + choice_nocase!(b"BINCLUDE") => parse_incbin(BinaryTransformation::None).parse_next(input), choice_nocase!(b"BUILDSNA") => parse_buildsna(true).parse_next(input), choice_nocase!(b"BUILDCPR") => Ok(LocatedTokenInner::BuildCpr), choice_nocase!(b"NOEXPORT") => parse_export(ExportKind::NoExport).parse_next(input), choice_nocase!(b"WAITNOPS") => parse_waitnops.parse_next(input), choice_nocase!(b"SNAPINIT") => parse_snainit.parse_next(input), - + _ => { input.reset(input_start); Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( @@ -2443,94 +2463,109 @@ fn parse_directive_of_size_8(input: &mut InnerZ80Span, input_start: &Checkpoint ErrorKind::Alt ))) } - } } -fn parse_directive_of_size_7(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { -match word { - choice_nocase!(b"INCLUDE") => parse_include.parse_next(input), - choice_nocase!(b"BANKSET") => parse_bankset.parse_next(input), - choice_nocase!(b"CHARSET") => parse_charset.parse_next(input), - choice_nocase!(b"PROTECT") => parse_protect.parse_next(input), - choice_nocase!(b"SECTION") => parse_section.parse_next(input), - choice_nocase!(b"SNAINIT") => parse_snainit.parse_next(input), +fn parse_directive_of_size_7( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { + match word { + choice_nocase!(b"INCLUDE") => parse_include.parse_next(input), + choice_nocase!(b"BANKSET") => parse_bankset.parse_next(input), + choice_nocase!(b"CHARSET") => parse_charset.parse_next(input), + choice_nocase!(b"PROTECT") => parse_protect.parse_next(input), + choice_nocase!(b"SECTION") => parse_section.parse_next(input), + choice_nocase!(b"SNAINIT") => parse_snainit.parse_next(input), - _ => { - input.reset(input_start); - Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( - input, - ErrorKind::Alt - ))) + _ => { + input.reset(input_start); + Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( + input, + ErrorKind::Alt + ))) + } } - -} } -fn parse_directive_of_size_6(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { - match word { - choice_nocase!(b"ASSERT") => parse_assert.parse_next(input), +fn parse_directive_of_size_6( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { + match word { + choice_nocase!(b"ASSERT") => parse_assert.parse_next(input), - choice_nocase!(b"EXPORT") => { - parse_export(ExportKind::Export).parse_next(input) - }, - choice_nocase!(b"INCBIN") => { - parse_incbin(BinaryTransformation::None).parse_next(input) - }, - #[cfg(not(target_arch = "wasm32"))] - choice_nocase!(b"INCEXO") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZEXO)) - .parse_next(input) - }, - #[cfg(not(target_arch = "wasm32"))] - choice_nocase!(b"INCLZ4") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ4)) - .parse_next(input) - }, + choice_nocase!(b"EXPORT") => parse_export(ExportKind::Export).parse_next(input), + choice_nocase!(b"INCBIN") => parse_incbin(BinaryTransformation::None).parse_next(input), + #[cfg(not(target_arch = "wasm32"))] + choice_nocase!(b"INCEXO") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZEXO)).parse_next(input) + }, + #[cfg(not(target_arch = "wasm32"))] + choice_nocase!(b"INCLZ4") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ4)).parse_next(input) + }, - choice_nocase!(b"INCL48") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ48)) - .parse_next(input) - }, + choice_nocase!(b"INCL48") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ48)).parse_next(input) + }, - choice_nocase!(b"INCL49") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ49)) - .parse_next(input) - }, + choice_nocase!(b"INCL49") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZ49)).parse_next(input) + }, - #[cfg(not(target_arch = "wasm32"))] - choice_nocase!(b"INCAPU") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZAPU)) - .parse_next(input) - }, + #[cfg(not(target_arch = "wasm32"))] + choice_nocase!(b"INCAPU") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZAPU)).parse_next(input) + }, - #[cfg(not(target_arch = "wasm32"))] - choice_nocase!(b"INCZX0") => { - parse_incbin(BinaryTransformation::Crunch(CrunchType::LZX0)) - .parse_next(input) - }, + #[cfg(not(target_arch = "wasm32"))] + choice_nocase!(b"INCZX0") => { + parse_incbin(BinaryTransformation::Crunch(CrunchType::LZX0)).parse_next(input) + }, - choice_nocase!(b"RETURN") => parse_return.parse_next(input), - choice_nocase!(b"SNASET") => parse_snaset(true).parse_next(input), + choice_nocase!(b"RETURN") => parse_return.parse_next(input), + choice_nocase!(b"SNASET") => parse_snaset(true).parse_next(input), - choice_nocase!(b"STRUCT") => parse_struct.parse_next(input), - choice_nocase!(b"TICKER") => parse_stable_ticker.parse_next(input), + choice_nocase!(b"STRUCT") => parse_struct.parse_next(input), + choice_nocase!(b"TICKER") => parse_stable_ticker.parse_next(input), - choice_nocase!(b"NOLIST") => Ok(LocatedTokenInner::NoList), + choice_nocase!(b"NOLIST") => Ok(LocatedTokenInner::NoList), - choice_nocase!(b"IMPORT") if is_orgams => parse_include.parse_next(input), /* TODO filter to remove the orgams specificies */ + choice_nocase!(b"IMPORT") if is_orgams => parse_include.parse_next(input), /* TODO filter to remove the orgams specificies */ - _ => { - input.reset(input_start); - Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( - input, - ErrorKind::Alt - ))) + _ => { + input.reset(input_start); + Err(ErrMode::Backtrack(Z80ParserError::from_error_kind( + input, + ErrorKind::Alt + ))) + } } } -} -fn parse_directive_of_size_5(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size_5( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { choice_nocase!(b"ALIGN") => parse_align.parse_next(input), choice_nocase!(b"LIMIT") => parse_limit.parse_next(input), @@ -2540,8 +2575,7 @@ fn parse_directive_of_size_5(input: &mut InnerZ80Span, input_start: &Checkpoint choice_nocase!(b"UNDEF") => parse_undef.parse_next(input), choice_nocase!(b"WRITE") => { - alt((parse_save(SaveKind::WriteDirect), parse_write_direct_memory)) - .parse_next(input) + alt((parse_save(SaveKind::WriteDirect), parse_write_direct_memory)).parse_next(input) }, _ => { @@ -2554,7 +2588,16 @@ fn parse_directive_of_size_5(input: &mut InnerZ80Span, input_start: &Checkpoint } } -fn parse_directive_of_size_4(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size_4( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { choice_nocase!(b"DEFB") | choice_nocase!(b"DEFM") @@ -2589,8 +2632,16 @@ fn parse_directive_of_size_4(input: &mut InnerZ80Span, input_start: &Checkpoint } } - -fn parse_directive_of_size3(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size3( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { choice_nocase!(b"BRK") if is_orgams => parse_breakpoint.parse_next(input), @@ -2613,7 +2664,16 @@ fn parse_directive_of_size3(input: &mut InnerZ80Span, input_start: &Checkpoint< } } -fn parse_directive_of_size_2(input: &mut InnerZ80Span, input_start: &Checkpoint, Located<&'static BStr>>, Stateful, &'static context::ParserContext>>, is_orgams: bool, within_struct: bool, word: &[u8]) -> PResult { +fn parse_directive_of_size_2( + input: &mut InnerZ80Span, + input_start: &Checkpoint< + Checkpoint, Located<&'static BStr>>, + Stateful, &'static context::ParserContext> + >, + is_orgams: bool, + within_struct: bool, + word: &[u8] +) -> PResult { match word { choice_nocase!(b"BY") if is_orgams => { parse_db_or_dw_or_str(DbDwStr::Db, within_struct).parse_next(input) @@ -2851,7 +2911,7 @@ pub fn parse_breakpoint(input: &mut InnerZ80Span) -> PResult { let mut address = address.borrow_mut(); let address = address.deref_mut(); - if address.is_some() { + if address.is_some() { false } else { @@ -2973,7 +3033,8 @@ pub fn parse_breakpoint(input: &mut InnerZ80Span) -> PResult Result<(), String> Task::Echo(_) => EchoRunner::default().run(task.args(), observer), Task::Extern(_) => ExternRunner::default().run(task.args(), observer), Task::Hideur(_) => HideurRunner::default().run(task.args(), observer), + Task::Snapshot(_) => SnapshotRunner::default().run(task.args(), observer), Task::ImgConverter(_) => ImgConverterRunner::default().run(task.args(), observer), Task::ImpDsk(_) => { DelegatedRunner { diff --git a/cpclib-bndbuild/src/runners/disc.rs b/cpclib-bndbuild/src/runners/disc.rs index 54499bba..9c3d89a9 100644 --- a/cpclib-bndbuild/src/runners/disc.rs +++ b/cpclib-bndbuild/src/runners/disc.rs @@ -60,8 +60,6 @@ impl Runner for DiscManagerRunner { type EventObserver = E; fn inner_run>(&self, itr: &[S], o: &E) -> Result<(), String> { - let matches = self.get_matches(itr)?; - let matches = self.get_matches(itr)?; if matches.get_flag("version") { o.emit_stdout(&self.get_clap_command().clone().render_version()); diff --git a/cpclib-bndbuild/src/runners/mod.rs b/cpclib-bndbuild/src/runners/mod.rs index db099759..2707c4a1 100644 --- a/cpclib-bndbuild/src/runners/mod.rs +++ b/cpclib-bndbuild/src/runners/mod.rs @@ -9,4 +9,5 @@ pub mod emulator; pub mod hideur; pub mod imgconverter; pub mod rm; +pub mod snapshot; pub mod xfer; diff --git a/cpclib-bndbuild/src/runners/snapshot.rs b/cpclib-bndbuild/src/runners/snapshot.rs new file mode 100644 index 00000000..c724a33b --- /dev/null +++ b/cpclib-bndbuild/src/runners/snapshot.rs @@ -0,0 +1,51 @@ +use std::marker::PhantomData; + +use cpclib_runner::event::EventObserver; +use cpclib_runner::runner::{Runner, RunnerWithClap}; +use cpclib_sna; + +use crate::built_info; +use crate::task::SNA_CMDS; + +pub const SNAPSHOT_CMD: &str = "SNAPSHOT"; + +pub struct SnapshotRunner { + command: clap::Command, + _phantom: PhantomData +} + +impl Default for SnapshotRunner { + fn default() -> Self { + let command = cpclib_sna::build_arg_parser(); + let command = command.no_binary_name(true).after_help(format!( + "{} {} embedded by {} {}", + cpclib_sna::built_info::PKG_NAME, + cpclib_sna::built_info::PKG_VERSION, + built_info::PKG_NAME, + built_info::PKG_VERSION + )); + Self { + command, + _phantom: Default::default() + } + } +} + +impl RunnerWithClap for SnapshotRunner { + fn get_clap_command(&self) -> &clap::Command { + &self.command + } +} + +impl Runner for SnapshotRunner { + type EventObserver = E; + + fn inner_run>(&self, itr: &[S], o: &E) -> Result<(), String> { + let matches = self.get_matches(itr)?; + cpclib_sna::process(&matches, o).map_err(|e| format!("{:?}", e)) + } + + fn get_command(&self) -> &str { + SNA_CMDS[0] + } +} diff --git a/cpclib-bndbuild/src/task.rs b/cpclib-bndbuild/src/task.rs index ad55f6f5..0e3072d5 100644 --- a/cpclib-bndbuild/src/task.rs +++ b/cpclib-bndbuild/src/task.rs @@ -32,6 +32,7 @@ pub enum Task { ImpDsk(StandardTaskArguments), Martine(StandardTaskArguments), Rm(StandardTaskArguments), + Snapshot(StandardTaskArguments), Xfer(StandardTaskArguments) } @@ -60,6 +61,7 @@ pub const HIDEUR_CMDS: &[&str] = &[HIDEUR_CMD]; pub const IMPDISC_CMDS: &[&str] = &[IMPDISC_CMD, "impdisc"]; pub const MARTINE_CMDS: &[&str] = &[MARTINE_CMD]; pub const RM_CMDS: &[&str] = &["rm", "del"]; +pub const SNA_CMDS: &[&str] = &["sna", "snpashot"]; pub const XFER_CMDS: &[&str] = &["xfer", "cpcwifi", "m4"]; impl Display for Task { @@ -78,7 +80,8 @@ impl Display for Task { Task::Martine(s) => (MARTINE_CMDS[0], s), Task::Rm(s) => (RM_CMDS[0], s), Task::Xfer(s) => (XFER_CMDS[0], s), - Task::Fap(s) => (FAP_CMDS[0], s) + Task::Fap(s) => (FAP_CMDS[0], s), + Task::Snapshot(s) => (SNA_CMDS[0], s) }; write!( @@ -107,20 +110,20 @@ macro_rules! is_some_cmd { #[rustfmt::skip] is_some_cmd!( - ace, amspirit, - basm, bndbuild, - cp, cpcec, - disc, - echo, emuctrl, r#extern, - fap, - hideur, - img2cpc, impdisc, - martine, - orgams, - rasm, rm, - sjasmplus, sugarbox, + ace, amspirit, + basm, bndbuild, + cp, cpcec, + disc, + echo, emuctrl, r#extern, + fap, + hideur, + img2cpc, impdisc, + martine, + orgams, + rasm, rm, + sjasmplus, sna, sugarbox, vasm, - winape, + winape, xfer ); @@ -198,6 +201,9 @@ impl<'de> Deserialize<'de> for Task { std )) } + else if is_sna_cmd(code) { + Ok(Task::Snapshot(std)) + } else if is_bndbuild_cmd(code) { Ok(Task::BndBuild(std)) } @@ -293,6 +299,7 @@ impl Task { | Task::Rm(t) | Task::Xfer(t) | Task::Emulator(_, t) + | Task::Snapshot(t) | Task::Fap(t) => t } } @@ -311,6 +318,7 @@ impl Task { | Task::BndBuild(t) | Task::Martine(t) | Task::Cp(t) + | Task::Snapshot(t) | Task::Emulator(_, t) | Task::Fap(t) => t } @@ -345,6 +353,7 @@ impl Task { Task::BndBuild(_) => false, Task::Disc(_) => false, Task::ImpDsk(_) => false, + Task::Snapshot(_) => false, Task::Cp(_) => false } } diff --git a/cpclib-bndbuild/tests/delegated/build.bnd b/cpclib-bndbuild/tests/delegated/build.bnd index 016c5bec..eaf6b85f 100644 --- a/cpclib-bndbuild/tests/delegated/build.bnd +++ b/cpclib-bndbuild/tests/delegated/build.bnd @@ -18,7 +18,7 @@ cmd: martine -in martine-logo.png -mode 1 -noheader -out martine.scr - tgt: clean - pnony: true + phony: true cmd: -rm martine.scr - tgt: distclean diff --git a/cpclib-disc/exec/catalog.rs b/cpclib-disc/exec/catalog.rs index 7235caf1..639e923d 100644 --- a/cpclib-disc/exec/catalog.rs +++ b/cpclib-disc/exec/catalog.rs @@ -186,10 +186,7 @@ fn main() -> std::io::Result<()> { let is_read_only = entry.is_read_only(); let fname = entry.format(); - let contains_control_chars = !fname - .as_str() - .chars() - .all(|c| c.is_ascii_graphic()); + let contains_control_chars = !fname.as_str().chars().all(|c| c.is_ascii_graphic()); if contains_id && !contains_control_chars { print!("{idx}. {fname}"); diff --git a/cpclib-runner/src/runner/emulator/sugarbox.rs b/cpclib-runner/src/runner/emulator/sugarbox.rs index cca7df6f..c2d06252 100644 --- a/cpclib-runner/src/runner/emulator/sugarbox.rs +++ b/cpclib-runner/src/runner/emulator/sugarbox.rs @@ -5,9 +5,6 @@ use std::fmt::Display; // cd SugarboxV2 // cmake . // make -j20 - - - use crate::delegated::{ ArchiveFormat, DownloadableInformation, ExecutableInformation, GithubCompiledApplication, GithubInformation @@ -68,8 +65,8 @@ impl ExecutableInformation for SugarBoxV2Version { #[cfg(target_os = "linux")] return match self { Self::V2_0_3 => "Sugarbox-2.0.3-Linux/Sugarbox", - Self::V2_0_2 => "Sugarbox-2.0.2-Linux/Sugarbox", - } + Self::V2_0_2 => "Sugarbox-2.0.2-Linux/Sugarbox" + }; } fn target_os_run_in_dir(&self) -> RunInDir { @@ -96,9 +93,8 @@ impl GithubInformation for SugarBoxV2Version { fn linux_key(&self) -> Option<&'static str> { match self { Self::V2_0_3 => Some("Sugarbox-2.0.3-Linux.tar.gz"), - Self::V2_0_2 => Some("Sugarbox-2.0.2-Linux.tar.gz"), + Self::V2_0_2 => Some("Sugarbox-2.0.2-Linux.tar.gz") } - } fn windows_key(&self) -> Option<&'static str> { @@ -110,11 +106,10 @@ impl GithubInformation for SugarBoxV2Version { fn macos_key(&self) -> Option<&'static str> { match self { - Self::V2_0_3 =>Some("Sugarbox-2.0.3-Darwin.tar.gz"), - Self::V2_0_2 =>Some("Sugarbox-2.0.2-Darwin.tar.gz"), + Self::V2_0_3 => Some("Sugarbox-2.0.3-Darwin.tar.gz"), + Self::V2_0_2 => Some("Sugarbox-2.0.2-Darwin.tar.gz") } } } - impl GithubCompiledApplication for SugarBoxV2Version {} diff --git a/cpclib-sna/Cargo.toml b/cpclib-sna/Cargo.toml index cab684da..1736e783 100644 --- a/cpclib-sna/Cargo.toml +++ b/cpclib-sna/Cargo.toml @@ -17,16 +17,17 @@ independent = true cpclib-common.workspace = true cfg-if.workspace = true -comfy-table = {version="7.1.3", optional = true} +comfy-table = { version = "7.1.3", optional = true } delegate.workspace = true +line-span = { version = "0.1.5", optional = true } num_enum.workspace = true nutype.workspace = true serde.workspace = true strum.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rustyline = { workspace = true, optional = true} -minus = {workspace=true, optional = true} +rustyline = { workspace = true, optional = true } +minus = { workspace = true, optional = true } [dev-dependencies] camino-tempfile.workspace = true @@ -37,10 +38,10 @@ built.workspace = true [features] default = [] -snapshot = ["cpclib-common/cmdline", "rustyline", "minus", "comfy-table"] - +cmdline = ["cpclib-common/cmdline", "comfy-table"] +interactive = ["rustyline", "minus", "line-span"] [[bin]] name = "snapshot" path = "src/bin/snapshot.rs" -required-features = ["snapshot"] +required-features = ["cmdline"] diff --git a/cpclib-sna/src/bin/snapshot.rs b/cpclib-sna/src/bin/snapshot.rs index 176d77d0..c7e87c0e 100644 --- a/cpclib-sna/src/bin/snapshot.rs +++ b/cpclib-sna/src/bin/snapshot.rs @@ -1,279 +1,7 @@ -#![deny( - missing_debug_implementations, - missing_copy_implementations, - trivial_casts, - trivial_numeric_casts, - unused_import_braces, - unused_qualifications, - nonstandard_style, - rust_2018_idioms, - unused, - warnings -)] -#![deny(clippy::pedantic)] -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::identity_op)] - -use std::str::FromStr; - -use comfy_table::{Table, *}; -use cpclib_common::clap::{Arg, ArgAction, Command}; -use cpclib_common::itertools::Itertools; -use cpclib_sna::{cli, Snapshot, SnapshotFlag}; - -pub mod built_info { - include!(concat!(env!("OUT_DIR"), "/built.rs")); -} - -/// Convert a string to its unsigned 32 bits representation (to access to extra memory) -#[must_use] -pub fn string_to_nb(source: &str) -> u32 { - let error = format!("Unable to read the value: {source}"); - if source.starts_with("0x") { - u32::from_str_radix(&source[2..], 16).expect(&error) - } - else if source.starts_with('%') { - u32::from_str_radix(&source[1..], 2).expect(&error) - } - else { - source.parse::().expect(&error) - } -} - -pub fn print_info(sna: &Snapshot) { - let mut table = Table::new(); - table.set_content_arrangement(ContentArrangement::Dynamic); - table.set_header(vec!["Flag", "Value"]); - table.add_rows( - SnapshotFlag::enumerate() - .iter() - .map(|flag| { - ( - flag.comment().lines().map(|l| l.trim()).join("\n"), - sna.get_value(flag) - ) - }) - .map(|(f, v)| vec![f.to_owned(), v.to_string()]) - ); - println!("{table}"); - - println!("# Chunks"); - for chunk in sna.chunks() { - chunk.print_info(); - } -} - fn main() { eprintln!("[WARNING] This is still a draft version that implement still few functionnalities"); - let desc_before = format!( - "Profile {} compiled: {}", - built_info::PROFILE, - built_info::BUILT_TIME_UTC - ); - let matches = Command::new("createSnapshot") - .version(built_info::PKG_VERSION) - .disable_version_flag(true) - .author("Krusty/Benediction") - .about("Amstrad CPC snapshot manipulation") - .before_help(desc_before) - .after_help("This tool tries to be similar than Ramlaid's one") - .arg(Arg::new("info") - .help("Display informations on the loaded snapshot") - .long("info") - .requires("inSnapshot") - .action(ArgAction::SetTrue) - ) - .arg(Arg::new("cli") - .help("Run the CLI interface for an interactive manipulation of snapshot") - .long("cli") - .requires("inSnapshot") - .action(ArgAction::SetTrue) - ) - .arg(Arg::new("debug") - .help("Display debugging information while manipulating the snapshot") - .long("debug") - .action(ArgAction::SetTrue) - ) - .arg(Arg::new("OUTPUT") - .help("Sets the output file to generate") - .conflicts_with("flags") - .conflicts_with("cli") - .conflicts_with("info") - .conflicts_with("getToken") - .last(true) - .required(true)) - .arg(Arg::new("inSnapshot") - .short('i') - .long("inSnapshot") - .value_name("INFILE") - .number_of_values(1) - .help("Load snapshot file") - ) - .arg(Arg::new("load") - .short('l') - .long("load") - .action(ArgAction::Append) - .number_of_values(2) - .help("Specify a file to include. -l fname address")) - .arg(Arg::new("getToken") - .short('g') - .long("getToken") - .action(ArgAction::Append) - .number_of_values(1) - .help("Display the value of a snapshot token") - .requires("inSnapshot") - ) - .arg(Arg::new("setToken") - .short('s') - .long("setToken") - .action(ArgAction::Append) - .number_of_values(2) - .help("Set snapshot token <$1> to value <$2>\nUse <$1>: to set array value\n\t\tex '-s CRTC_REG:6 20' : Set CRTC register 6 to 20")) - .arg(Arg::new("putData") - .short('p') - .long("putData") - .action(ArgAction::Append) - .number_of_values(2) - .help("Put <$2> byte at <$1> address in snapshot memory") - - ) - .arg(Arg::new("version") - .short('v') - .long("version") - .number_of_values(1) - .value_parser(["1", "2", "3"]) - .help("Version of the saved snapshot.") - .default_value("3") - ) - .arg(Arg::new("flags") - .help("List the flags and exit") - .long("flags") - .action(ArgAction::SetTrue) - ) - .get_matches(); - - // Display all tokens - if matches.get_flag("flags") { - for flag in SnapshotFlag::enumerate().iter() { - println!( - "{:?} / {:?} bytes.{}", - flag, - flag.elem_size(), - flag.comment() - ); - } - return; - } - - // Load a snapshot or generate an empty one - let mut sna = if matches.contains_id("inSnapshot") { - let fname = matches.get_one::("inSnapshot").unwrap(); - let path = Utf8Path::new(&fname); - Snapshot::load(path).expect("Error while loading the snapshot") - } - else { - Snapshot::default() - }; - - // Activate the debug mode - sna.debug = matches.contains_id("debug"); - - if matches.get_flag("info") { - print_info(&sna); - return; - } - - if matches.get_flag("cli") { - let fname = matches.get_one::("inSnapshot").unwrap(); - cli::cli(fname, sna); - return; - } - - // Manage the files insertion - if matches.contains_id("load") { - let loads = matches - .get_many::("load") - .unwrap() - .collect::>(); - for i in 0..(loads.len() / 2) { - let fname = loads[i * 2 + 0]; - let place = loads[i * 2 + 1]; - - let address = { - if place.starts_with("0x") { - u32::from_str_radix(&place[2..], 16).unwrap() - } - else if place.starts_with('0') { - u32::from_str_radix(&place[1..], 8).unwrap() - } - else { - place.parse::().unwrap() - } - }; - sna.add_file(fname, address as usize) - .expect("Unable to add file"); - } - } - - // Patch memory - if matches.contains_id("putData") { - let data = matches - .get_many::("putData") - .unwrap() - .collect::>(); - - for i in 0..(data.len() / 2) { - let address = string_to_nb(data[i * 2 + 0]); - let value = string_to_nb(data[i * 2 + 1]); - assert!(value < 0x100); - - sna.set_byte(address, value as u8); - } - } - - // Read the tokens - if matches.contains_id("getToken") { - for token in matches.get_many::("getToken").unwrap() { - let token = SnapshotFlag::from_str(token).unwrap(); - println!("{:?} => {}", &token, sna.get_value(&token)); - } - return; - } - - // Set the tokens - if matches.contains_id("setToken") { - let loads = matches - .get_many::("setToken") - .unwrap() - .collect::>(); - for i in 0..(loads.len() / 2) { - // Read the parameters from the command line - let token = dbg!(loads[i * 2 + 0]); - let token = SnapshotFlag::from_str(token).unwrap(); - - let value = { - let source = loads[i * 2 + 1]; - string_to_nb(source) - }; - - // Get the token - sna.set_value(token, value as u16).unwrap(); - - sna.log(format!( - "Token {token:?} set at value {value} (0x{value:x})" - )); - } - } - - let fname = matches.get_one::("OUTPUT").unwrap(); - let version = matches - .get_one::("version") - .unwrap() - .parse::() - .unwrap() - .try_into() - .unwrap(); - sna.save(fname, version) - .expect("Unable to save the snapshot"); + let parser = cpclib_sna::build_arg_parser(); + let matches = parser.get_matches(); + cpclib_sna::process(&matches, &()).unwrap(); } diff --git a/cpclib-sna/src/cli.rs b/cpclib-sna/src/cli.rs index 14b2a883..18609cbd 100644 --- a/cpclib-sna/src/cli.rs +++ b/cpclib-sna/src/cli.rs @@ -90,11 +90,12 @@ impl Command { match self { Command::Load2(fname) => { use cpclib_common::resolve_path::*; - let path = &fname.resolve(); + let path = fname.resolve(); + let path = Utf8Path::from_path(path.as_ref()).unwrap(); Snapshot::load(&path) .map(|s| sna2.replace((fname.clone(), s))) .map_err(|e| { - eprintln!("Error while loading {}. {}", path.display(), e); + eprintln!("Error while loading {}. {}", path, e); }); }, Command::Memory(from, amount) => { diff --git a/cpclib-sna/src/error.rs b/cpclib-sna/src/error.rs index 6e5f19d4..eab7efea 100644 --- a/cpclib-sna/src/error.rs +++ b/cpclib-sna/src/error.rs @@ -1,9 +1,12 @@ -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +use std::path::Display; + +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub enum SnapshotError { FileError, NotEnougSpaceAvailable, InvalidValue, FlagDoesNotExists, - InvalidIndex + InvalidIndex, + AnyError(String) } diff --git a/cpclib-sna/src/lib.rs b/cpclib-sna/src/lib.rs index 474df82e..2ba7be4d 100644 --- a/cpclib-sna/src/lib.rs +++ b/cpclib-sna/src/lib.rs @@ -6,7 +6,13 @@ use std::ops::Deref; use cpclib_common::bitvec::vec::BitVec; use cpclib_common::camino::Utf8Path; +#[cfg(feature = "cmdline")] +use cpclib_common::parse_value; use cpclib_common::riff::{RiffChunk, RiffCode}; +#[cfg(feature = "cmdline")] +use cpclib_common::winnow::error::ContextError; +#[cfg(feature = "cmdline")] +use cpclib_common::winnow::stream::AsBStr; use num_enum::TryFromPrimitive; mod chunks; @@ -15,14 +21,32 @@ pub mod flags; mod memory; pub mod parse; -#[cfg(feature = "snapshot")] +#[cfg(feature = "cmdline")] +use cpclib_common::clap::{Arg, ArgAction, ArgMatches, Command}; + +#[cfg(feature = "interactive")] pub mod cli; +#[cfg(feature = "cmdline")] +use std::str::FromStr; pub use chunks::*; +#[cfg(feature = "cmdline")] +use comfy_table::{Table, *}; +#[cfg(feature = "cmdline")] +use cpclib_common::itertools::Itertools; pub use error::*; pub use flags::*; pub use memory::*; +#[cfg(feature = "cmdline")] +fn string_to_nb>(s: S) -> Result { + let s = s.as_ref(); + let mut bytes = s.as_bstr(); + parse_value::<_, ContextError>(&mut bytes) + .map_err(|e| format!("Unable to parse {}. {}", s, e.to_string())) + .map_err(|s| SnapshotError::AnyError(s)) +} + /// ! Re-implementation of createsnapshot by Ramlaid/Arkos /// ! in rust by Krusty/Benediction @@ -825,6 +849,265 @@ impl Snapshot { } } +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +#[cfg(feature = "cmdline")] +pub fn print_info(sna: &Snapshot) { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + table.set_header(vec!["Flag", "Value"]); + table.add_rows( + SnapshotFlag::enumerate() + .iter() + .map(|flag| { + ( + flag.comment().lines().map(|l| l.trim()).join("\n"), + sna.get_value(flag) + ) + }) + .map(|(f, v)| vec![f.to_owned(), v.to_string()]) + ); + println!("{table}"); + + println!("# Chunks"); + for chunk in sna.chunks() { + chunk.print_info(); + } +} + +#[cfg(feature = "cmdline")] +use cpclib_common::event::EventObserver; + +// TODO: use observers instead of printing on terminal ! +#[cfg(feature = "cmdline")] +pub fn process(matches: &ArgMatches, o: &E) -> Result<(), SnapshotError> { + // Display all tokens + + if matches.get_flag("flags") { + for flag in SnapshotFlag::enumerate().iter() { + o.emit_stdout(&format!( + "{:?} / {:?} bytes.{}", + flag, + flag.elem_size(), + flag.comment() + )); + } + return Ok(()); + } + + // Load a snapshot or generate an empty one + let mut sna = if matches.contains_id("inSnapshot") { + let fname = matches.get_one::("inSnapshot").unwrap(); + let path = Utf8Path::new(&fname); + Snapshot::load(path) + .map_err(|e| SnapshotError::AnyError(format!("Unable to load file {fname}. {e}")))? + } + else { + Snapshot::default() + }; + + // Activate the debug mode + sna.debug = matches.contains_id("debug"); + + if matches.get_flag("info") { + print_info(&sna); + return Ok(()); + } + + #[cfg(feature = "interactive")] + if matches.get_flag("cli") { + let fname = matches.get_one::("inSnapshot").unwrap(); + cli::cli(fname, sna); + return Ok(()); + } + + // Manage the files insertion + if matches.contains_id("load") { + let loads = matches + .get_many::("load") + .unwrap() + .collect::>(); + for i in 0..(loads.len() / 2) { + let fname = loads[i * 2 + 0]; + let place = loads[i * 2 + 1]; + + let address = { + if place.starts_with("0x") { + u32::from_str_radix(&place[2..], 16).unwrap() + } + else if place.starts_with('0') { + u32::from_str_radix(&place[1..], 8).unwrap() + } + else { + place.parse::().unwrap() + } + }; + sna.add_file(fname, address as usize) + .expect("Unable to add file"); + } + } + + // Patch memory + if matches.contains_id("putData") { + let data = matches + .get_many::("putData") + .unwrap() + .collect::>(); + + for i in 0..(data.len() / 2) { + let address = string_to_nb(data[i * 2 + 0])?; + let value = string_to_nb(data[i * 2 + 1])?; + assert!(value < 0x100); + + sna.set_byte(address, value as u8); + } + } + + // Read the tokens + if matches.contains_id("getToken") { + for token in matches.get_many::("getToken").unwrap() { + let token = SnapshotFlag::from_str(token).unwrap(); + println!("{:?} => {}", &token, sna.get_value(&token)); + } + return Ok(()); + } + + // Set the tokens + if matches.contains_id("setToken") { + let loads = matches + .get_many::("setToken") + .unwrap() + .collect::>(); + for i in 0..(loads.len() / 2) { + // Read the parameters from the command line + let token = dbg!(loads[i * 2 + 0]); + let token = SnapshotFlag::from_str(token).unwrap(); + + let value = { + let source = loads[i * 2 + 1]; + string_to_nb(source)? + }; + + // Get the token + sna.set_value(token, value as u16).unwrap(); + + sna.log(format!( + "Token {token:?} set at value {value} (0x{value:x})" + )); + } + } + + let fname = matches.get_one::("OUTPUT").unwrap(); + let version = matches + .get_one::("version") + .unwrap() + .parse::() + .unwrap() + .try_into() + .unwrap(); + sna.save(fname, version) + .expect("Unable to save the snapshot"); + + Ok(()) +} + +#[cfg(feature = "cmdline")] +pub fn build_arg_parser() -> Command { + let desc_before = format!( + "Profile {} compiled: {}", + built_info::PROFILE, + built_info::BUILT_TIME_UTC + ); + + let cmd = Command::new("createSnapshot") + .version(built_info::PKG_VERSION) + .disable_version_flag(true) + .author("Krusty/Benediction") + .about("Amstrad CPC snapshot manipulation") + .before_help(desc_before) + .after_help("This tool tries to be similar than Ramlaid's one") + .arg(Arg::new("info") + .help("Display informations on the loaded snapshot") + .long("info") + .requires("inSnapshot") + .action(ArgAction::SetTrue) + ) + .arg(Arg::new("debug") + .help("Display debugging information while manipulating the snapshot") + .long("debug") + .action(ArgAction::SetTrue) + ) + .arg(Arg::new("OUTPUT") + .help("Sets the output file to generate") + .conflicts_with("flags") + .conflicts_with("info") + .conflicts_with("getToken") + .last(true) + .required(true)) + .arg(Arg::new("inSnapshot") + .short('i') + .long("inSnapshot") + .value_name("INFILE") + .number_of_values(1) + .help("Load snapshot file") + ) + .arg(Arg::new("load") + .short('l') + .long("load") + .action(ArgAction::Append) + .number_of_values(2) + .help("Specify a file to include. -l fname address")) + .arg(Arg::new("getToken") + .short('g') + .long("getToken") + .action(ArgAction::Append) + .number_of_values(1) + .help("Display the value of a snapshot token") + .requires("inSnapshot") + ) + .arg(Arg::new("setToken") + .short('s') + .long("setToken") + .action(ArgAction::Append) + .number_of_values(2) + .help("Set snapshot token <$1> to value <$2>\nUse <$1>: to set array value\n\t\tex '-s CRTC_REG:6 20' : Set CRTC register 6 to 20")) + .arg(Arg::new("putData") + .short('p') + .long("putData") + .action(ArgAction::Append) + .number_of_values(2) + .help("Put <$2> byte at <$1> address in snapshot memory") + + ) + .arg(Arg::new("version") + .short('v') + .long("version") + .number_of_values(1) + .value_parser(["1", "2", "3"]) + .help("Version of the saved snapshot.") + .default_value("3") + ) + .arg(Arg::new("flags") + .help("List the flags and exit") + .long("flags") + .action(ArgAction::SetTrue) + ); + + #[cfg(feature = "interactive")] + let cmd = cmd.arg( + Arg::new("cli") + .help("Run the CLI interface for an interactive manipulation of snapshot") + .long("cli") + .requires("inSnapshot") + .conflicts_with("OUTPUT") + .action(ArgAction::SetTrue) + ); + + cmd +} + #[cfg(test)] mod tests { use similar_asserts::assert_eq;