MODULE Bin2Hex;	(** AUTHOR "negelef"; PURPOSE "Binary file to Intel Hex-file converter"; *)

IMPORT Streams, Files, Commands;

PROCEDURE Byte (VAR w: Streams.Writer; val: LONGINT);
BEGIN w.Hex (val MOD 100H, -2);
END Byte;

PROCEDURE ExtendedAddressRecord* (VAR w: Streams.Writer; extadr: LONGINT);
BEGIN
	w.Char (':'); Byte (w, 2); Byte (w, 0); Byte (w, 0); Byte (w, 4);
	Byte (w, extadr DIV 100H); Byte (w, extadr);
	Byte (w, 100H - (2 + 0 + 0 + 4 + extadr DIV 100H MOD 100H + extadr MOD 100H) MOD 100H); w.Ln;
END ExtendedAddressRecord;

PROCEDURE DataRecord* (VAR w: Streams.Writer; CONST data: ARRAY OF CHAR; len, offset: LONGINT);
VAR checksum, i: LONGINT;
BEGIN
	checksum := len MOD 100H;
	INC (checksum, offset DIV 100H MOD 100H + offset MOD 100H);
	w.Char (':'); Byte (w, len); Byte (w, offset DIV 100H); Byte (w, offset); Byte (w, 0);
	FOR i := 0 TO len - 1 DO Byte (w, ORD (data[i])); INC (checksum, ORD (data[i])) END;
	Byte (w, 100H - checksum MOD 100H); w.Ln;
END DataRecord;

PROCEDURE EndOfFileRecord* (VAR w: Streams.Writer);
BEGIN w.Char (':'); Byte (w, 0); Byte (w, 0); Byte (w, 0); Byte (w, 1); Byte (w, 255); w.Ln;
END EndOfFileRecord;

PROCEDURE ConvertFile* (r: Streams.Reader; w: Streams.Writer; offset, maxlen: LONGINT);
VAR len, extadr: LONGINT; c: CHAR; data: POINTER TO ARRAY  OF CHAR;
BEGIN
	NEW(data,maxlen+1);
	extadr := offset DIV 10000H;
	IF extadr # 0 THEN ExtendedAddressRecord (w, extadr); END;

	REPEAT
		len := 0;
		LOOP
			r.Char (c); IF r.res # Files.Ok THEN EXIT END;
			data[len] := c; INC (len); IF len = maxlen THEN EXIT END;
		END;

		IF len # 0 THEN DataRecord (w, data^, len, offset) END;

		INC (offset, len);
		IF offset DIV 10000H # extadr THEN
			extadr := offset DIV 10000H;
			ExtendedAddressRecord (w, extadr);
		END;
	UNTIL r.res # Files.Ok;

	EndOfFileRecord (w);
END ConvertFile;

PROCEDURE Convert* (context: Commands.Context);
VAR
	source, dest: ARRAY Files.NameLength OF CHAR;
	offset, maxlen: LONGINT;
	src, dst: Files.File;
	r: Files.Reader; w: Files.Writer;
BEGIN
	context.arg.SkipWhitespace; context.arg.String (source);
	context.arg.SkipWhitespace; context.arg.String (dest);
	IF ~context.arg.GetInteger (offset, TRUE) THEN offset := 0 END;
	IF ~context.arg.GetInteger (maxlen, TRUE) THEN maxlen := 255 END;

	src := Files.Old (source); dst := Files.New (dest);
	IF src = NIL THEN
		context.error.String ("failed to open binary file '"); context.error.String (source); context.error.Char ("'"); context.error.Ln;
		RETURN
	END;

	Files.OpenReader (r, src, 0); Files.OpenWriter (w, dst, 0);
	ConvertFile (r, w, offset, maxlen);

	w.Update;
	Files.Register (dst);
END Convert;

END Bin2Hex.