Ошибки PascalABC.NET

Понял, уже занимаюсь этим. А Вы сможете поверить правильность?

А как насчёт мне ответить? Сломанную совместимость вы хоть дадите исправить? И, улучшать - я буду, только разрешите. Конечно, совместимость это важно и я её оставлю.

Вставьте в мою программу вывод (например, в файл типа Text) и сверите потом его.

Вот модификация программы, запустите ее и получите в директории запуска два файла - бинарник и текстовый. Когда своей прочитаете, должно получиться то, что в текстовом.

Summary
type
  t1 = record
    f1: real;
    f2: integer;
    f3: string;
    f4: integer;
    f5: string
  end;

begin
  var f: file;
  var b: t1;
  Assign(f, 'MyFile.bin');
  Rewrite(f);
  var ft := OpenWrite('Protocol.txt');
  loop Random(3, 5) do
  begin
    b.f1 := Random;
    b.f2 := Random(1, 500);
    var s := String('');
    loop b.f2 do
      s += ChrAnsi(Random(32, 255));
    b.f3 := s;
    b.f4 := Random(1, 500);
    s := String('');
    loop b.f4 do
      s += ChrAnsi(Random(32, 255));
    b.f5 := s;
    Write(f, b);
    Writeln(ft, b)
  end;
  f.Close;
  ft.Close
end.

Нет, по 2 раза на строку, всего 4. Тип file использует System.IO.BinaryWriter чтоб сохранить строку, а он, в свою очередь, сохраняет длину строки в 7-битном формате, перед самой строкой, поэтому получается 4 дескриптора, по 2 на строку.

Мне, если честно, до фонаря как работает низкоуровневое обеспечение. Я пишу на уровне Паскаля, сохраняя в переменной то, чего хочу. Считайте, что это дополнительные поля, если они Вам не нужны - типизированный файл может быть любой структуры!

А сколько байт символ занимает?

В разной кодировке - разное. В оперативке обычно записано в юникоде, поэтому по 2. Ну а в файле бинарные файлы паскаля сохраняюсь по 1 байту на символ.

Похоже, что один. А что мешает получить оба файла и посмотреть в бинарнике?

Собственно, речь о том, что запись произвольных данных в файл легко через бинарник моделируется. Через типизированный файл - никогда. Да и зачем, собственно, если проблема решается через бинарный? Как я уже упоминал, есть такой метод Seek, позволяющий стать перед нужной записью. На типизированном файле он реализован очень просто и эффективно. Зная длину L и номер записи n (от 0) мы сдвигаемся на n*L байт от начала файла и читаем L байт в буфер длиной L А как быть, если длина записи гуляет?

Ничего не понял из этого. Какая совместимость сломана?

Там в файле какая-то дичь. Сейчас разбираться буду.

@Sun_Serega пишет о том, что в ТурбоПаскаль дескриптор фактической длины строки занимает 1 байт. В реализации PascalABC.NET пока длина 0-127 байт, все совпадает. А для длины от 128 байт PascalABC.NET почему-то формирует уже двухбайтный дескриптор, записывая длину в семибитной кодировке. Это он называет несовместимостью на уровне структуры файла.

Так и не смог разобраться в том, что генерирует Паскаль, поэтому чистый .NET:

Uses System.IO;
Uses System;

type
  T1 = Record
    public Name: String;
    public Family: String;
    public Age: Int32;
    public procedure Serialize(S: Stream);
    begin
      var bw := new BinaryWriter(S);
      bw.Write(self.Age);
      bw.Write(Int32(self.Name.Length));
      for Var i := 1 to Name.Length do
      begin
        bw.Write(Int16(self.Name.Chars[i]));
      end;
      bw.Write(Int32(self.Family.Length));
      for Var i := 1 to Family.Length do
      begin
        bw.Write(Int16(self.Family.Chars[i]));
      end;
    end;
    
    public Class function Deserialize(S: Stream): t1;
    begin
      var br := new BinaryReader(S);
      Result.Age := br.ReadInt32();
      var l: Int32 := br.ReadInt32();
      for Var i := 0 to l - 1 do
      begin
        Result.Name += Char(br.ReadInt16());
      end;
      l := br.ReadInt32();
      for Var i := 0 to l - 1 do
      begin
        Result.Family += Char(br.ReadInt16());
      end;
    end;
    
    public procedure Print();
    begin
      Console.WriteLine(self.Age);
      Console.WriteLine(self.Family);
      Console.WriteLine(self.Name);
    end;
  End;

begin
  var v1: t1;
  v1.Age := 25;
  v1.Family := 'Пупкин';
  v1.Name := 'Вася';
  var fs := System.IO.File.Create('MyFile.bin');
  v1.Serialize(fs);
  v1.Family := 'Иванов';
  v1.Age := 20;
  v1.Serialize(fs);
  fs.Close();
  fs := System.IO.File.OpenRead('MyFile.bin');
  while fs.Position < fs.Length do
  begin
    var v2 := t1.Deserialize(fs);
    v2.Print();
    Console.WriteLine();
  end;
  fs.Close();
end.

И? Расскажите, как и насколько быстро стать в таком файле на запись номер 153234 ?

Очень, очень долго, если считывать всё. А если попробовать писать в заголовке разметку, то моментально.

Нет. Только если построите дополнительный файл “указателей” на начало каждой записи. Как следствие, у Вас Seek работать будет крайне неэффективно. А это главное, для чего делались типизированные файлы. Без него ни поиска не построить, ни внешней сортировки.

Поэтому я уже третий день, как дятел, долблю, что нельзя реализовать типизированные файлы со строками неограниченной длины. Файлы - можно, но это уже не будут паскалевские типизированные файлы.

Но тут ведь оказалось, что в PascalABC.NET структура файла не та. То есть, как правильно отметил @Sun_Serega, надо лишь поправить несколько строчек - и всё будет безразмерным. Или же наоборот, размерным. Но мне кажется, что эффективнее всего использовать не паскалевские файлы, а потоки из System.IO и средства для работы с ними. Получается и гибче и эффективнее.

Что значит “не та”? С дескрипторами на 1 байт длиннее для полей, с длиной большей 127? Но при этом осталось главное - длина записи фиксированная! Следовательно - работает seek так, как предписано свыше. Кому нужны безразмерные строки в типизированном файле ценой крайне тормозной работы seek ?

А вот и нет. Если дескриптор строки занимает 2 байта - длина всей записи тоже увеличивается на 1. И это то что Я “долблю”, как вы выразились)). Но, кстати, не 3 а 2, в первый день разговор было не много о другом.

В целом - да, @Gleb говорит вообще о другом, более гибкая реализация с прямым использованием System.IO - это уже не типизированные файлы. А типизированные файлы должны иметь старую структуру, ради совместимости.

Наконец-то стало понятно, о чем Вы! Если записать две записи, в одной фактически заполнить меньше 128 байт, а в другой - больше, то они окажутся разной длины? Вы это сказать хотите?

Да, вот, к примеру:

type
  r1 = record
    x1: byte;
    s: string[128];
    x2: byte;
  end;

begin
  
  var f:file of r1;
  Rewrite(f,'temp.bin');
  var a1,a2,a3:r1;
  
  a1.x1 := $FF;
  a1.s := 'abc';
  a1.x2 := $EE;
  a2.x1 := $DD;
  a2.s := '*'*128;
  a2.x2 := $CC;
  a3.x1 := $BB;
  a3.s := 'abc';
  a3.x2 := $AA;
  
  f.Write(a1,a2,a3);
  
  f.Close;
  
end.

Я тут добавил ещё поля по 1 байту до и после строк, чтоб видно было что записи друг на друга не налазят. Да и в целом, чтоб лучше было видно края каждой записи.
Тут a1 и a3 занимают 131 байт, а a2 занимает 132 байт.

Я думал что это будет само собой разумеющееся. Если в каком то месте тратится больше памяти чем должно - значит в другом или 2 записи сожрут кусок друг друга, или у них будет разная длинна. И то и то неприемлемо.

2 лайка