Laufzeit-Eingabe und Zonenzustand korrigieren und Change archivieren
This commit is contained in:
@@ -52,7 +52,7 @@ pub struct Feld {
|
||||
pub struct Datei {
|
||||
pub modus: Modus,
|
||||
pub pfad: PathBuf,
|
||||
datei: File,
|
||||
datei: BufReader<File>,
|
||||
/// Recordlänge (`RANDOM`); bei `BINARY` immer 1.
|
||||
pub reclen: usize,
|
||||
/// Recordpuffer für `FIELD`/`GET`/`PUT`.
|
||||
@@ -61,8 +61,8 @@ pub struct Datei {
|
||||
/// Nächste Position: Datensatz bei `RANDOM`, Byte bei `BINARY`,
|
||||
/// jeweils 1-basiert.
|
||||
pub position: u64,
|
||||
/// Zeilenpuffer für sequenzielles Lesen.
|
||||
leser: Option<BufReader<File>>,
|
||||
/// Ein Komma kann auch vor EOF noch ein leeres Feld eröffnen.
|
||||
leeres_feld: bool,
|
||||
}
|
||||
|
||||
impl Datei {
|
||||
@@ -82,11 +82,7 @@ impl Datei {
|
||||
opt.read(true).write(true).create(true);
|
||||
}
|
||||
}
|
||||
let datei = opt.open(pfad).map_err(fehler_aus_io)?;
|
||||
let leser = match modus {
|
||||
Modus::Input => Some(BufReader::new(File::open(pfad).map_err(fehler_aus_io)?)),
|
||||
_ => None,
|
||||
};
|
||||
let datei = BufReader::new(opt.open(pfad).map_err(|e| fehler_am_pfad(e, pfad))?);
|
||||
let reclen = if modus == Modus::Random {
|
||||
reclen.max(1)
|
||||
} else {
|
||||
@@ -100,13 +96,14 @@ impl Datei {
|
||||
puffer: vec![b' '; reclen],
|
||||
felder: Vec::new(),
|
||||
position: 1,
|
||||
leser,
|
||||
leeres_feld: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Dateigröße in Bytes.
|
||||
pub fn laenge(&self) -> Result<u64, RuntimeError> {
|
||||
self.datei
|
||||
.get_ref()
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(fehler_aus_io)
|
||||
@@ -117,8 +114,7 @@ impl Datei {
|
||||
pub fn eof(&mut self) -> Result<bool, RuntimeError> {
|
||||
match self.modus {
|
||||
Modus::Input => {
|
||||
let l = self.leser.as_mut().ok_or(RuntimeError(52))?;
|
||||
Ok(l.fill_buf().map_err(fehler_aus_io)?.is_empty())
|
||||
Ok(!self.leeres_feld && self.datei.fill_buf().map_err(fehler_aus_io)?.is_empty())
|
||||
}
|
||||
Modus::Random => Ok((self.position - 1) * self.reclen as u64 >= self.laenge()?),
|
||||
Modus::Binary => Ok(self.position > self.laenge()?),
|
||||
@@ -132,24 +128,143 @@ impl Datei {
|
||||
if !matches!(self.modus, Modus::Output | Modus::Append) {
|
||||
return Err(RuntimeError(54)); // Bad file mode
|
||||
}
|
||||
self.datei.write_all(text.as_bytes()).map_err(fehler_aus_io)
|
||||
self.datei
|
||||
.get_mut()
|
||||
.write_all(text.as_bytes())
|
||||
.map_err(fehler_aus_io)
|
||||
}
|
||||
|
||||
/// Eine Zeile lesen (ohne Zeilenende); `None` = Dateiende.
|
||||
/// `SEEK`: auch den Lesepuffer verwerfen; Position bleibt 1-basiert.
|
||||
pub fn position_setzen(&mut self, pos: u64) -> Result<(), RuntimeError> {
|
||||
if pos == 0 {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
if self.modus == Modus::Input {
|
||||
self.datei
|
||||
.seek(SeekFrom::Start(pos - 1))
|
||||
.map_err(fehler_aus_io)?;
|
||||
}
|
||||
self.position = pos;
|
||||
self.leeres_feld = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn byte_lesen(&mut self) -> Result<Option<u8>, RuntimeError> {
|
||||
let mut b = [0];
|
||||
if self.datei.read(&mut b).map_err(fehler_aus_io)? == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
self.position += 1;
|
||||
self.leeres_feld = false;
|
||||
Ok(Some(b[0]))
|
||||
}
|
||||
|
||||
fn crlf_beenden(&mut self, b: u8) -> Result<(), RuntimeError> {
|
||||
if b == b'\r' && self.datei.fill_buf().map_err(fehler_aus_io)?.first() == Some(&b'\n') {
|
||||
self.byte_lesen()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Eine Zeile ab dem gemeinsamen Cursor, ohne genau ein Zeilenende.
|
||||
pub fn zeile_lesen(&mut self) -> Result<Option<String>, RuntimeError> {
|
||||
if self.modus != Modus::Input {
|
||||
return Err(RuntimeError(54));
|
||||
}
|
||||
let l = self.leser.as_mut().ok_or(RuntimeError(52))?;
|
||||
let mut roh = Vec::new();
|
||||
let n = l.read_until(b'\n', &mut roh).map_err(fehler_aus_io)?;
|
||||
if n == 0 {
|
||||
let mut gelesen = false;
|
||||
self.leeres_feld = false;
|
||||
while let Some(b) = self.byte_lesen()? {
|
||||
gelesen = true;
|
||||
if matches!(b, b'\r' | b'\n') {
|
||||
self.crlf_beenden(b)?;
|
||||
break;
|
||||
}
|
||||
roh.push(b);
|
||||
}
|
||||
if !gelesen {
|
||||
return Ok(None);
|
||||
}
|
||||
while roh.last() == Some(&b'\n') || roh.last() == Some(&b'\r') {
|
||||
roh.pop();
|
||||
String::from_utf8(roh)
|
||||
.map(Some)
|
||||
.map_err(|_| RuntimeError(57))
|
||||
}
|
||||
|
||||
/// Nächstes INPUT-#-Feld, ohne nachfolgende Felder oder Zeilen zu verbrauchen.
|
||||
pub fn feld_lesen(&mut self) -> Result<String, RuntimeError> {
|
||||
if self.modus != Modus::Input {
|
||||
return Err(RuntimeError(54));
|
||||
}
|
||||
Ok(Some(String::from_utf8_lossy(&roh).into_owned()))
|
||||
let mut roh = Vec::new();
|
||||
let mut in_quote = false;
|
||||
let mut hatte_quote = false;
|
||||
let mut gelesen = std::mem::take(&mut self.leeres_feld);
|
||||
while let Some(b) = self.byte_lesen()? {
|
||||
gelesen = true;
|
||||
match b {
|
||||
b'"' if !hatte_quote && roh.iter().all(u8::is_ascii_whitespace) => {
|
||||
roh.clear();
|
||||
in_quote = true;
|
||||
hatte_quote = true;
|
||||
}
|
||||
b'"' if in_quote => {
|
||||
if self.datei.fill_buf().map_err(fehler_aus_io)?.first() == Some(&b'"') {
|
||||
self.byte_lesen()?;
|
||||
roh.push(b'"');
|
||||
} else {
|
||||
in_quote = false;
|
||||
}
|
||||
}
|
||||
b',' if !in_quote => {
|
||||
self.leeres_feld = true;
|
||||
break;
|
||||
}
|
||||
b'\r' | b'\n' if !in_quote => {
|
||||
self.crlf_beenden(b)?;
|
||||
break;
|
||||
}
|
||||
b if hatte_quote && !in_quote && b.is_ascii_whitespace() => {}
|
||||
_ => roh.push(b),
|
||||
}
|
||||
}
|
||||
if !gelesen || in_quote {
|
||||
return Err(RuntimeError(62));
|
||||
}
|
||||
let text = String::from_utf8(roh).map_err(|_| RuntimeError(57))?;
|
||||
Ok(if hatte_quote {
|
||||
text
|
||||
} else {
|
||||
text.trim().to_owned()
|
||||
})
|
||||
}
|
||||
|
||||
/// INPUT$: exakt n UTF-8-Codepoints; beschädigte Daten sind Fehler 57.
|
||||
pub fn zeichen_lesen(&mut self, n: usize) -> Result<String, RuntimeError> {
|
||||
if !matches!(self.modus, Modus::Input | Modus::Binary) {
|
||||
return Err(RuntimeError(54));
|
||||
}
|
||||
if self.modus == Modus::Binary {
|
||||
self.datei
|
||||
.seek(SeekFrom::Start(self.position - 1))
|
||||
.map_err(fehler_aus_io)?;
|
||||
}
|
||||
let mut text = String::new();
|
||||
for _ in 0..n {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0] = self.byte_lesen()?.ok_or(RuntimeError(62))?;
|
||||
let breite = match bytes[0] {
|
||||
0..=0x7f => 1,
|
||||
0xc2..=0xdf => 2,
|
||||
0xe0..=0xef => 3,
|
||||
0xf0..=0xf4 => 4,
|
||||
_ => return Err(RuntimeError(57)),
|
||||
};
|
||||
for b in &mut bytes[1..breite] {
|
||||
*b = self.byte_lesen()?.ok_or(RuntimeError(57))?;
|
||||
}
|
||||
text.push_str(std::str::from_utf8(&bytes[..breite]).map_err(|_| RuntimeError(57))?);
|
||||
}
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
/// Datensatz `nr` (1-basiert) in den Puffer lesen.
|
||||
@@ -179,7 +294,10 @@ impl Datei {
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.map_err(fehler_aus_io)?;
|
||||
let puffer = std::mem::take(&mut self.puffer);
|
||||
self.datei.write_all(&puffer).map_err(fehler_aus_io)?;
|
||||
self.datei
|
||||
.get_mut()
|
||||
.write_all(&puffer)
|
||||
.map_err(fehler_aus_io)?;
|
||||
self.puffer = puffer;
|
||||
self.position = nr + 1;
|
||||
Ok(())
|
||||
@@ -211,13 +329,16 @@ impl Datei {
|
||||
self.datei
|
||||
.seek(SeekFrom::Start(pos - 1))
|
||||
.map_err(fehler_aus_io)?;
|
||||
self.datei.write_all(daten).map_err(fehler_aus_io)?;
|
||||
self.datei
|
||||
.get_mut()
|
||||
.write_all(daten)
|
||||
.map_err(fehler_aus_io)?;
|
||||
self.position = pos + daten.len() as u64;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schliessen(&mut self) -> Result<(), RuntimeError> {
|
||||
self.datei.flush().map_err(fehler_aus_io)
|
||||
self.datei.get_mut().flush().map_err(fehler_aus_io)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,9 +364,6 @@ impl Dateien {
|
||||
return Err(RuntimeError(55)); // File already open
|
||||
}
|
||||
let p = pfad_normieren(pfad);
|
||||
if modus == Modus::Input && !p.exists() {
|
||||
return Err(RuntimeError(53)); // File not found
|
||||
}
|
||||
self.offen
|
||||
.insert(nummer, Datei::oeffnen(&p, modus, reclen)?);
|
||||
Ok(())
|
||||
@@ -478,6 +596,7 @@ pub fn pfad_normieren(p: &str) -> PathBuf {
|
||||
fn fehler_aus_io(e: std::io::Error) -> RuntimeError {
|
||||
use std::io::ErrorKind::*;
|
||||
match e.kind() {
|
||||
NotADirectory => RuntimeError(76), // Path not found
|
||||
NotFound => RuntimeError(53), // File not found
|
||||
PermissionDenied => RuntimeError(70), // Permission denied
|
||||
AlreadyExists => RuntimeError(58), // File already exists
|
||||
@@ -485,32 +604,39 @@ fn fehler_aus_io(e: std::io::Error) -> RuntimeError {
|
||||
}
|
||||
}
|
||||
|
||||
/// NotFound am Dateieintrag (53) gegenüber fehlendem Elternpfad (76).
|
||||
fn fehler_am_pfad(e: std::io::Error, pfad: &Path) -> RuntimeError {
|
||||
if e.kind() == std::io::ErrorKind::NotFound
|
||||
&& pfad
|
||||
.parent()
|
||||
.is_some_and(|p| !p.as_os_str().is_empty() && !p.is_dir())
|
||||
{
|
||||
RuntimeError(76)
|
||||
} else {
|
||||
fehler_aus_io(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// `KILL` — Datei löschen.
|
||||
pub fn loeschen(pfad: &str) -> Result<(), RuntimeError> {
|
||||
std::fs::remove_file(pfad_normieren(pfad)).map_err(fehler_aus_io)
|
||||
let p = pfad_normieren(pfad);
|
||||
std::fs::remove_file(&p).map_err(|e| fehler_am_pfad(e, &p))
|
||||
}
|
||||
|
||||
/// `NAME alt AS neu`.
|
||||
pub fn umbenennen(alt: &str, neu: &str) -> Result<(), RuntimeError> {
|
||||
let (a, n) = (pfad_normieren(alt), pfad_normieren(neu));
|
||||
if !a.exists() {
|
||||
return Err(RuntimeError(53));
|
||||
}
|
||||
std::fs::metadata(&a).map_err(|e| fehler_am_pfad(e, &a))?;
|
||||
if n.exists() {
|
||||
return Err(RuntimeError(58)); // File already exists
|
||||
}
|
||||
std::fs::rename(a, n).map_err(fehler_aus_io)
|
||||
std::fs::rename(&a, &n).map_err(|e| fehler_am_pfad(e, &n))
|
||||
}
|
||||
|
||||
/// `MKDIR` / `RMDIR` / `CHDIR`.
|
||||
pub fn verzeichnis_anlegen(pfad: &str) -> Result<(), RuntimeError> {
|
||||
let p = pfad_normieren(pfad);
|
||||
if p.parent()
|
||||
.is_some_and(|e| !e.as_os_str().is_empty() && !e.exists())
|
||||
{
|
||||
return Err(RuntimeError(76)); // Path not found
|
||||
}
|
||||
std::fs::create_dir(p).map_err(fehler_aus_io)
|
||||
std::fs::create_dir(&p).map_err(|e| fehler_am_pfad(e, &p))
|
||||
}
|
||||
|
||||
pub fn verzeichnis_entfernen(pfad: &str) -> Result<(), RuntimeError> {
|
||||
@@ -613,7 +739,7 @@ pub fn felder_trennen(zeile: &str) -> Vec<String> {
|
||||
/// Ein Element für `WRITE #` aufbereiten: Strings in Anführungszeichen.
|
||||
pub fn write_element(text: &str, ist_string: bool) -> String {
|
||||
if ist_string {
|
||||
format!("\"{text}\"")
|
||||
format!("\"{}\"", text.replace('"', "\"\""))
|
||||
} else {
|
||||
text.trim().to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user