DataIO.Mod 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. (* CAPO - Computational Analysis Platform for Oberon - by Alan Freed and Felix Friedrich. *)
  2. (* Version 1, Update 2 *)
  3. MODULE DataIO; (** AUTHOR "adf, fof"; PURPOSE "File IO for making scientific data types persistent"; *)
  4. (** A template exists for creating persistent OBJECT's that use the reader and writer defined in this module.
  5. This template is found in: DataTemplate.Mod. *)
  6. IMPORT Modules, Streams, Files, Dates, NbrInt, NbrRat, NbrRe, NbrCplx, NbrStrings, DataErrors;
  7. TYPE
  8. Item = POINTER TO RECORD
  9. type: Modules.TypeDesc;
  10. load: LoadProc;
  11. store: StoreProc;
  12. next: Item
  13. END;
  14. Registry = POINTER TO RECORD
  15. root: Item
  16. END;
  17. Card = OBJECT
  18. VAR number: NbrInt.Integer;
  19. type: Modules.TypeDesc;
  20. next: Card;
  21. PROCEDURE Read( R: Reader );
  22. VAR res: WORD;
  23. msg: ARRAY 64 OF CHAR;
  24. string: NbrStrings.String; module: Modules.Module; moduleName, typeName: Modules.Name;
  25. BEGIN
  26. NbrInt.Load( R, number ); R.RawString( moduleName ); R.RawString( typeName );
  27. module := Modules.ThisModule( moduleName, res, msg ); NEW( string, 64 );
  28. IF module # NIL THEN
  29. type := Modules.ThisType( module, typeName );
  30. IF type = NIL THEN
  31. string := "Type "; string := string + moduleName; string := string + "."; string := string + typeName; string := string + " does not exist on your computer.";
  32. DataErrors.Error( string^ )
  33. END
  34. ELSE
  35. string := "Module "; string := string + moduleName; string := string + " does not exist on your computer.";
  36. DataErrors.Error( string^ ); HALT( 1000 ); (* Must halt here, further reading is catastrophic. *)
  37. END
  38. END Read;
  39. PROCEDURE Write( W: Writer );
  40. BEGIN
  41. NbrInt.Store( W, number ); W.RawString( type.mod.name ); W.RawString( type.name )
  42. END Write;
  43. END Card;
  44. Library = OBJECT
  45. VAR entries: NbrInt.Integer;
  46. root, card: Card;
  47. PROCEDURE & Initialize*;
  48. BEGIN
  49. entries := 0; NEW( root ); root.number := 0; card := root
  50. END Initialize;
  51. PROCEDURE Read( R: Reader );
  52. VAR i: NbrInt.Integer; new: Card;
  53. BEGIN
  54. card := root; NbrInt.Load( R, entries );
  55. FOR i := 1 TO entries DO NEW( new ); new.Read( R ); card.next := new; card := card.next END;
  56. card := root
  57. END Read;
  58. PROCEDURE Write( W: Writer );
  59. VAR i: NbrInt.Integer;
  60. BEGIN
  61. card := root; NbrInt.Store( W, entries );
  62. FOR i := 1 TO entries DO card := card.next; card.Write( W ) END;
  63. card := root
  64. END Write;
  65. PROCEDURE Push( type: Modules.TypeDesc );
  66. VAR new: Card;
  67. BEGIN
  68. card := root;
  69. IF type # NIL THEN
  70. LOOP
  71. IF card.next = NIL THEN
  72. NEW( new ); new.number := card.number + 1; new.type := type; card.next := new; card := new;
  73. NbrInt.Inc( entries ); EXIT
  74. END;
  75. IF type = card.type THEN (* This library card already exists. *) EXIT END;
  76. card := card.next
  77. END
  78. END
  79. END Push;
  80. END Library;
  81. (** Reader for reading data from a .Data file. *)
  82. Reader* = OBJECT (Files.Reader)
  83. VAR lib: Library;
  84. file: File;
  85. PROCEDURE ReaderAvailable( ): BOOLEAN;
  86. BEGIN
  87. IF res = Streams.Ok THEN RETURN TRUE
  88. ELSIF res = Streams.EOF THEN DataErrors.Error( "Attempted to read past the end of file." ); RETURN FALSE
  89. ELSIF res = Streams.FormatError THEN DataErrors.Error( "A format error encounter by the reader." ); RETURN FALSE
  90. ELSE DataErrors.IntError( res, "The 'res' error number originating from an Streams reader error." ); RETURN FALSE
  91. END
  92. END ReaderAvailable;
  93. (** Reads a date and time. *)
  94. PROCEDURE DateTime*( VAR x: Dates.DateTime );
  95. VAR d, t: LONGINT;
  96. BEGIN
  97. IF ReaderAvailable() THEN RawNum( d ); RawNum( t ); x := Dates.OberonToDateTime( d, t ) ELSE HALT( 1000 ) END
  98. END DateTime;
  99. (** Reads an integer number. *)
  100. PROCEDURE Integer*( VAR x: NbrInt.Integer );
  101. BEGIN
  102. IF ReaderAvailable() THEN NbrInt.Load( SELF, x ) ELSE HALT( 1000 ) END
  103. END Integer;
  104. (** Reads a rational number. *)
  105. PROCEDURE Rational*( VAR x: NbrRat.Rational );
  106. BEGIN
  107. IF ReaderAvailable() THEN NbrRat.Load( SELF, x ) ELSE HALT( 1000 ) END
  108. END Rational;
  109. (** Reads a complex number. *)
  110. PROCEDURE Complex*( VAR x: NbrCplx.Complex );
  111. BEGIN
  112. IF ReaderAvailable() THEN NbrCplx.Load( SELF, x ) ELSE HALT( 1000 ) END
  113. END Complex;
  114. (** Reads a dynamic 0X-terminated string. *)
  115. PROCEDURE PtrString*( VAR x: NbrStrings.String );
  116. BEGIN
  117. IF ReaderAvailable() THEN NbrStrings.Load( SELF, x ) ELSE HALT( 1000 ) END
  118. END PtrString;
  119. (*
  120. (* Reads a dynamic POINTER TO ARRAY of some type that has been registered via the PlugIn procedure. *)
  121. PROCEDURE PtrArray*( VAR x: ANY );
  122. BEGIN
  123. IF ReaderAvailable() THEN
  124. ELSE HALT(1000)
  125. END
  126. END PtrArray;
  127. *)
  128. (** Reads a dynamic object whose type has been registered via the PlugIn procedure. *)
  129. PROCEDURE Object*( VAR x: OBJECT );
  130. VAR n: NbrInt.Integer; item: Item;
  131. BEGIN
  132. IF ReaderAvailable() THEN
  133. Integer( n );
  134. IF n > 0 THEN
  135. lib.card := lib.root;
  136. LOOP
  137. lib.card := lib.card.next;
  138. IF lib.card = NIL THEN DataErrors.Error( "Corrupt file - sought library card does not exist." ); HALT( 1001 ); EXIT END;
  139. IF n = lib.card.number THEN
  140. item := registry.root;
  141. LOOP
  142. item := item.next;
  143. IF item = NIL THEN DataErrors.Warning( "Encountered an alien object to be read from file." ); HALT( 1002 ); EXIT END;
  144. IF lib.card.type = item.type THEN item.load( SELF, x ); EXIT END
  145. END;
  146. EXIT
  147. END
  148. END
  149. ELSE (* The object stored was the NIL pointer. *) x := NIL
  150. END;
  151. lib.card := lib.root
  152. ELSE HALT( 1000 )
  153. END
  154. END Object;
  155. END Reader;
  156. (** Writer for writing data to a .Data file. *)
  157. Writer* = OBJECT (Files.Writer);
  158. VAR lib: Library;
  159. file: File;
  160. (** Writes a date and time. *)
  161. PROCEDURE DateTime*( x: Dates.DateTime );
  162. VAR d, t: LONGINT;
  163. BEGIN
  164. Dates.DateTimeToOberon( x, d, t ); RawNum( d ); RawNum( t ); Update
  165. END DateTime;
  166. (** Writes an integer number. *)
  167. PROCEDURE Integer*( x: NbrInt.Integer );
  168. BEGIN
  169. NbrInt.Store( SELF, x ); Update
  170. END Integer;
  171. (** Writes a rational number. *)
  172. PROCEDURE Rational*( x: NbrRat.Rational );
  173. BEGIN
  174. NbrRat.Store( SELF, x ); Update
  175. END Rational;
  176. (** Writes a real number. *)
  177. PROCEDURE Real*( x: NbrRe.Real );
  178. BEGIN
  179. NbrRe.Store( SELF, x ); Update
  180. END Real;
  181. (** Writes a complex number. *)
  182. PROCEDURE Complex*( x: NbrCplx.Complex );
  183. BEGIN
  184. NbrCplx.Store( SELF, x ); Update
  185. END Complex;
  186. (** Writes a dynamic 0X-terminated string. *)
  187. PROCEDURE PtrString*( x: NbrStrings.String );
  188. BEGIN
  189. NbrStrings.Store( SELF, x ); Update
  190. END PtrString;
  191. (*
  192. (* Writes a dynamic POINTER TO ARRAY of some type that has been registered via the PlugIn procedure. *)
  193. PROCEDURE PtrArray*( x: ANY );
  194. VAR
  195. BEGIN
  196. END PtrArray;
  197. *)
  198. (** Writes a dynamic object whose type has been registered via the PlugIn procedure. *)
  199. PROCEDURE Object*( x: OBJECT );
  200. VAR item: Item;
  201. BEGIN
  202. IF x # NIL THEN
  203. lib.Push( Modules.TypeOf( x ) ); item := registry.root;
  204. LOOP
  205. item := item.next;
  206. IF item = NIL THEN DataErrors.Error( "Attempted to write an object whose type has not been registered." ); HALT( 1003 ); EXIT END;
  207. IF lib.card.type = item.type THEN Integer( lib.card.number ); item.store( SELF, x ); EXIT END
  208. END
  209. ELSE (* Object is NIL. *) Integer( 0 )
  210. END;
  211. Update
  212. END Object;
  213. END Writer;
  214. (** File type for files with a .Data extension. *)
  215. File* = OBJECT (** Not shareable between multiple processes. *)
  216. VAR F: Files.File;
  217. lib: Library;
  218. initialwpos: LONGINT;
  219. R-: Reader;
  220. W-: Writer;
  221. PROCEDURE & Initialize*;
  222. BEGIN
  223. NEW( lib ); initialwpos := 0
  224. END Initialize;
  225. PROCEDURE Length*( ): LONGINT;
  226. BEGIN
  227. RETURN F.Length()
  228. END Length;
  229. END File;
  230. (** Loading procedure type for registering an object for data IO. *)
  231. LoadProc* = PROCEDURE ( R: Reader; VAR obj: OBJECT );
  232. (** Storing procedure type for registering an object for data IO. *)
  233. StoreProc* = PROCEDURE ( W: Writer; obj: OBJECT );
  234. VAR
  235. registry: Registry;
  236. (** Register the load and store procedures that belong to the dynamic object to be made persistent. *)
  237. PROCEDURE PlugIn*( obj: ANY; load: LoadProc; store: StoreProc );
  238. VAR item, new: Item; type: Modules.TypeDesc; string: NbrStrings.String;
  239. BEGIN
  240. IF obj # NIL THEN
  241. IF load # NIL THEN
  242. IF store # NIL THEN
  243. type := Modules.TypeOf( obj ); item := registry.root;
  244. LOOP
  245. IF type = item.type THEN (* already registered *) EXIT END;
  246. IF item.next = NIL THEN
  247. NEW( new ); new.type := type; new.load := load; new.store := store; item.next := new; EXIT
  248. END;
  249. item := item.next
  250. END
  251. ELSE NEW( string, 64 ); string := "A NIL 'store' procedure was sent for registation from module "; string := string + type.mod.name; string := string + "."; DataErrors.Error( string^ )
  252. END
  253. ELSE NEW( string, 64 ); string := "A NIL 'load' procedure was sent for registation from module "; string := string + type.mod.name; string := string + "."; DataErrors.Error( string^ )
  254. END
  255. ELSE NEW( string, 64 ); string := "A NIL 'obj' was sent for registation from module "; string := string + type.mod.name; string := string + "."; DataErrors.Error( string^ )
  256. END
  257. END PlugIn;
  258. (** Open a .Data file.
  259. The reader is placed at the beginning of the file.
  260. The writer is placed at the end of the file, which is at the beginning if the file is new,
  261. thereby preventing existing data from being overwritten. *)
  262. PROCEDURE Open*( fileName: Files.FileName ): File;
  263. VAR existingFile: BOOLEAN; dummy, libPos: LONGINT; file: File; libR: Reader;
  264. name, fName: ARRAY Files.NameLength OF CHAR;
  265. suffix: ARRAY Files.PrefixLength OF CHAR;
  266. BEGIN
  267. (* Create the file. *)
  268. NEW( file ); Files.SplitExtension( fileName, name, suffix ); Files.JoinExtension( name, "Data", fName );
  269. file.F := Files.Old( fName );
  270. IF file.F # NIL THEN existingFile := TRUE ELSE existingFile := FALSE; file.F := Files.New( fName ) END;
  271. (* Attach the reader. *)
  272. NEW( file.R, file.F, 0 ); file.R.file := file;
  273. IF existingFile THEN
  274. (* Read in the library. *)
  275. file.R.RawLInt( libPos ); NEW( libR, file.F, libPos ); file.lib.Read( libR )
  276. END;
  277. file.R.lib := file.lib;
  278. (* Attach the writer. *)
  279. IF existingFile THEN
  280. (* Append any new data. *)
  281. NEW( file.W, file.F, libPos ); file.W.InitFileWriter( file.F, libPos );
  282. file.initialwpos := libPos; (* Position of a Writer is relative to initial point *)
  283. ELSE
  284. NEW( file.W, file.F, 0 );
  285. (* Handle the place holder locating the library. *)
  286. file.W.RawLInt( dummy ); file.R.RawLInt( dummy )
  287. END;
  288. file.W.lib := file.lib; file.W.file := file; RETURN file
  289. END Open;
  290. (** Opening an existing file places the writer at the end of that file, by default, therefore all future
  291. writings append the file. Calling Rewind moves the reader and writer to the beginning of the file,
  292. and will therefore overwrite all contents previously held by the file. All prior data will be lost. *)
  293. PROCEDURE Rewind*( f: File );
  294. VAR dummy: LONGINT;
  295. BEGIN
  296. IF f # NIL THEN
  297. NEW( f.lib ); NEW( f.R, f.F, 0 ); NEW( f.W, f.F, 0 ); f.initialwpos := 0; f.R.lib := f.lib; f.R.file := f; f.W.lib := f.lib; f.W.file := f;
  298. (* Handle the place holder locating the library. *)
  299. f.W.RawLInt( dummy ); f.R.RawLInt( dummy )
  300. END
  301. END Rewind;
  302. (** Close a .Data file.
  303. Executing this command attaches a hidden library needed to allocate memory to restore stored PTR variables.
  304. It is therefore imperative that every file opened with Open gets physically closed with this command. *)
  305. PROCEDURE Close*( f: File );
  306. VAR libPosW: Writer;
  307. BEGIN
  308. f.W.Update; NEW( libPosW, f.F, 0 ); (* writing library position to very first position *)
  309. libPosW.RawLInt( f.W.Pos() + f.initialwpos ); libPosW.Update; f.lib.Write( f.W );
  310. f.W.Update; Files.Register( f.F ); f.F := NIL; f.lib := NIL; f.R := NIL; f.W := NIL
  311. END Close;
  312. BEGIN
  313. NEW( registry ); NEW( registry.root )
  314. END DataIO.