?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Object Code Management : Correction Generation' ??
MODULE ocm$build_corrector;
?? PUSH (LISTEXT := ON) ??
*copyc occ$corrector
*copyc oce$metapatch_generator_errors
*copyc oct$breaklist
*copyc oct$corrector
*copyc oct$offset
?? POP ??
*copyc amp$get_segment_pointer
*copyc i#move
*copyc fsp$close_file
*copyc fsp$open_file
*copyc osp$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc osp$set_status_abnormal
*copyc pmp$get_unique_name

  TYPE
    byte_array = array [1 .. * ] of 0 .. 0ff(16);

?? NEWTITLE := '[XDCL] ocp$build_corrector', EJECT ??
*copyc och$build_corrector

  PROCEDURE [XDCL] ocp$build_corrector
    (    old_breaklist: ^oct$breaklist;
         new_breaklist: ^oct$breaklist;
         p_second_inter_ol: ^SEQ ( * );
         p_new_ol: ^SEQ ( * );
         length_of_old_breaklist: oct$breaklist_length;
         length_of_new_breaklist: oct$breaklist_length;
     VAR compressed_corrector: ^SEQ ( * );
     VAR status: ost$status);

    VAR
      attachment_options: array [1 .. 3] of fst$attachment_option,
      bytes_ok: oct$offset,
      bytes_to_delete: oct$offset,
      bytes_to_insert: oct$offset,
      corrector: ^SEQ ( * ),
      corrector_header: ^oct$corrector_header,
      corrector_item_size: 0 .. 256,
      from: ^cell,
      i: oct$breaklist_length,
      j: oct$breaklist_length,
      local_status: ost$status,
      match_found: boolean,
      new: oct$breaklist_length,
      new_breaklist_bytes: ^byte_array,
      new_current_position_pointer: ^cell,
      new_length: oct$breaklist_length,
      new_ol: ^SEQ ( * ),
      new_start: oct$breaklist_length,
      offset: oct$offset,
      old: oct$breaklist_length,
      old_breaklist_bytes: ^byte_array,
      old_current_position_pointer: ^cell,
      old_length: oct$breaklist_length,
      old_start: oct$breaklist_length,
      second_inter_ol: ^SEQ ( * ),
      temp_cor: ost$name,
      temp_cor_fid: amt$file_identifier,
      temp_cor_file_opened: boolean,
      temp_cor_seg: amt$segment_pointer;

?? NEWTITLE := 'abort_handler', EJECT ??

{ PURPOSE:
{   This procedure cleans up when an abort situation occurs
{   within the block structure.
{
{ DESIGN:
{   If the file has been opened, it will be closed before the
{   the procedure returns.
{
{ NOTES:
{
{

    PROCEDURE abort_handler
      (    condition: pmt$condition;
           condition_information: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      VAR
        ignore_status: ost$status;

      IF temp_cor_file_opened THEN
        fsp$close_file (temp_cor_fid, ignore_status);
      IFEND;

    PROCEND abort_handler;

?? OLDTITLE, EJECT ??

    new_ol := p_new_ol;
    second_inter_ol := p_second_inter_ol;

    pmp$get_unique_name (temp_cor, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    osp$establish_block_exit_hndlr (^abort_handler);

  /main/
    BEGIN

      attachment_options [1].selector := fsc$access_and_share_modes;
      attachment_options [1].access_modes.selector := fsc$specific_access_modes;
      attachment_options [1].access_modes.value := $fst$file_access_options
            [fsc$read, fsc$shorten, fsc$append, fsc$modify];
      attachment_options [1].share_modes.selector := fsc$determine_from_access_modes;
      attachment_options [2].selector := fsc$create_file;
      attachment_options [2].create_file := TRUE;
      attachment_options [3].selector := fsc$wait_for_attachment;
      attachment_options [3].wait_for_attachment.wait := osc$wait;
      attachment_options [3].wait_for_attachment.wait_time := fsc$longest_wait_time;

      temp_cor_file_opened := TRUE;
      fsp$open_file (temp_cor, amc$segment, ^attachment_options, NIL, NIL, NIL, NIL, temp_cor_fid, status);
      IF NOT status.normal THEN
        EXIT /main/;
      IFEND;

      amp$get_segment_pointer (temp_cor_fid, amc$sequence_pointer, temp_cor_seg, status);
      IF NOT status.normal THEN
        EXIT /main/;
      IFEND;
      corrector := temp_cor_seg.sequence_pointer;

      RESET corrector;
      NEXT corrector_header IN corrector;
      corrector_header^.version := occ$corrector_header_version;
      corrector_header^.number_of_correctors := 0;
      corrector_header^.size := #SIZE (corrector_header^);
      new := 1;
      corrector_item_size := #SIZE (oct$corrector_item);
      FOR old := 1 TO length_of_old_breaklist DO
        match_found := FALSE;
        WHILE NOT match_found DO
          IF new > length_of_new_breaklist THEN
            osp$set_status_abnormal (occ$status_id, oce$error_in_correction_gen, 'building corrector',
                  status);
            EXIT /main/;
          IFEND;
          match_found := (old_breaklist^ [old].module_name = new_breaklist^ [new].module_name) AND
                (old_breaklist^ [old].major_name = new_breaklist^ [new].major_name) AND
                (old_breaklist^ [old].minor_name = new_breaklist^ [new].minor_name) AND
                (old_breaklist^ [old].kind = new_breaklist^ [new].kind) AND
                (old_breaklist^ [old].section_ordinal = new_breaklist^ [new].section_ordinal) AND
                (old_breaklist^ [old].secondary_section_ordinal =
                new_breaklist^ [new].secondary_section_ordinal);
          IF NOT match_found THEN
            from := #ADDRESS (#RING (new_ol), #SEGMENT (new_ol), new_breaklist^ [new].offset);
            build_corrector_item (0, 0, new_breaklist^ [new].length, from, corrector, corrector_header);
            new := new + 1;
          IFEND;
        WHILEND;
        old_current_position_pointer := #ADDRESS (#RING (second_inter_ol), #SEGMENT (second_inter_ol),
              old_breaklist^ [old].offset);
        new_current_position_pointer := #ADDRESS (#RING (new_ol), #SEGMENT (new_ol),
              new_breaklist^ [new].offset);
        RESET second_inter_ol TO old_current_position_pointer;
        RESET new_ol TO new_current_position_pointer;
        old_length := old_breaklist^ [old].length;
        new_length := new_breaklist^ [new].length;
        NEXT old_breaklist_bytes: [1 .. old_length] IN second_inter_ol;
        NEXT new_breaklist_bytes: [1 .. new_length] IN new_ol;
        old_start := 1;
        new_start := 1;

        REPEAT
          IF (old_start <= old_length) AND (new_start <= new_length) THEN
            search_until_difference (old_breaklist_bytes, new_breaklist_bytes, old_start, new_start,
                  bytes_ok);
            old_start := old_start + bytes_ok;
            new_start := new_start + bytes_ok;

            IF (old_start <= old_length) AND (new_start <= new_length) THEN
              search_until_match (old_breaklist^ [old].kind, old_breaklist_bytes, new_breaklist_bytes,
                    old_start, new_start, bytes_to_delete, bytes_to_insert);
              old_start := old_start + bytes_to_delete;
              new_start := new_start + bytes_to_insert;
            ELSE
              bytes_to_insert := new_length - new_start + 1;
              bytes_to_delete := old_length - old_start + 1;
              old_start := old_start + bytes_to_delete;
              new_start := new_start + bytes_to_insert;
            IFEND;
          ELSE
            bytes_ok := 0;
            bytes_to_delete := old_length - old_start + 1;
            bytes_to_insert := new_length - new_start + 1;
            old_start := old_start + bytes_to_delete;
            new_start := new_start + bytes_to_insert;
          IFEND;
          IF (bytes_ok > 0) AND (bytes_ok < corrector_item_size) AND NOT ((bytes_to_delete = 0) AND
                (bytes_to_insert = 0)) THEN
            bytes_to_delete := bytes_to_delete + bytes_ok;
            bytes_to_insert := bytes_to_insert + bytes_ok;
            bytes_ok := 0;
          IFEND;
          IF bytes_to_insert > 0 THEN
            offset := new_breaklist^ [new].offset + new_start - bytes_to_insert - 1;
            from := #ADDRESS (#RING (new_ol), #SEGMENT (new_ol), offset);
          ELSE
            from := NIL;
          IFEND;
          build_corrector_item (bytes_ok, bytes_to_delete, bytes_to_insert, from, corrector,
                corrector_header);
        UNTIL (old_start > old_length) OR (new_start > new_length);
        new := new + 1;
      FOREND;

      IF new <= length_of_new_breaklist THEN
        j := 0;
        FOR i := new TO length_of_new_breaklist DO
          j := j + new_breaklist^ [i].length;
        FOREND;
        IF j > 0 THEN
          from := #ADDRESS (#RING (new_ol), #SEGMENT (new_ol), new_breaklist^ [new].offset);
        ELSE
          from := NIL;
        IFEND;
        build_corrector_item (0, 0, j, from, corrector, corrector_header);
      IFEND;

      compress_corrector (corrector, compressed_corrector);

    END /main/;

    IF temp_cor_file_opened THEN
      fsp$close_file (temp_cor_fid, local_status);
    IFEND;

    osp$disestablish_cond_handler;

    IF status.normal AND (NOT local_status.normal) THEN
      status := local_status;
    IFEND;

  PROCEND ocp$build_corrector;
?? OLDTITLE ??
?? NEWTITLE := 'compress_corrector', EJECT ??

{ PURPOSE:
{    The purpose of this request is compress the corrector.  Upon entry
{ the corrector contains at least one item for each breaklist item.  This
{ procedure combines corrector items that do not specify any bytes to delete
{ or insert with its adjacent corrector item.

  PROCEDURE compress_corrector
    (    p_old_corrector: ^SEQ ( * );
     VAR new_corrector: ^SEQ ( * ));

    VAR
      i: oct$corrector_size,
      index: oct$corrector_size,
      j: oct$corrector_size,
      new_bytes: ^oct$new_bytes,
      new_corrector_header: ^oct$corrector_header,
      new_item: ^oct$corrector_item,
      old_corrector: ^SEQ ( * ),
      old_corrector_header: ^oct$corrector_header,
      old_item: ^oct$corrector_item,
      save_bytes: ^oct$new_bytes,
      temp_bytes: ^oct$new_bytes,
      temp_item: oct$corrector_item;

    old_corrector := p_old_corrector;

    ALLOCATE save_bytes: [1 .. 10000000];
    RESET old_corrector;
    RESET new_corrector;
    NEXT old_corrector_header IN old_corrector;
    NEXT new_corrector_header IN new_corrector;
    new_corrector_header^ := old_corrector_header^;
    new_corrector_header^.size := #SIZE (new_corrector_header^);
    new_corrector_header^.number_of_correctors := 0;

    index := 1;
    NEXT old_item IN old_corrector;
    temp_item := old_item^;
    IF old_item^.bytes_to_insert > 0 THEN
      NEXT temp_bytes: [1 .. old_item^.bytes_to_insert] IN old_corrector;
      FOR j := 1 TO old_item^.bytes_to_insert DO
        save_bytes^ [index] := temp_bytes^ [j];
        index := index + 1;
      FOREND;
    IFEND;

    FOR i := 2 TO old_corrector_header^.number_of_correctors DO
      NEXT old_item IN old_corrector;
      IF (old_item^.bytes_ok > 0) AND NOT ((temp_item.bytes_to_delete = 0) AND
            (temp_item.bytes_to_insert = 0)) THEN
        NEXT new_item IN new_corrector;
        new_item^ := temp_item;
        new_corrector_header^.size := new_corrector_header^.size + #SIZE (new_item^);
        IF new_item^.bytes_to_insert > 0 THEN
          NEXT new_bytes: [1 .. new_item^.bytes_to_insert] IN new_corrector;
          FOR j := 1 TO new_item^.bytes_to_insert DO
            new_bytes^ [j] := save_bytes^ [j];
          FOREND;
          index := 1;
          new_corrector_header^.size := new_corrector_header^.size + #SIZE (new_bytes^);
        IFEND;
        new_corrector_header^.number_of_correctors := new_corrector_header^.number_of_correctors + 1;
        temp_item := old_item^;
      ELSE
        temp_item.bytes_ok := temp_item.bytes_ok + old_item^.bytes_ok;
        temp_item.bytes_to_delete := temp_item.bytes_to_delete + old_item^.bytes_to_delete;
        temp_item.bytes_to_insert := temp_item.bytes_to_insert + old_item^.bytes_to_insert;
      IFEND;
      IF old_item^.bytes_to_insert > 0 THEN
        NEXT temp_bytes: [1 .. old_item^.bytes_to_insert] IN old_corrector;
        FOR j := 1 TO old_item^.bytes_to_insert DO
          save_bytes^ [index] := temp_bytes^ [j];
          index := index + 1;
        FOREND;
      IFEND;
    FOREND;
    NEXT new_item IN new_corrector;
    new_item^ := temp_item;
    new_corrector_header^.size := new_corrector_header^.size + #SIZE (new_item^);
    IF new_item^.bytes_to_insert > 0 THEN
      NEXT new_bytes: [1 .. new_item^.bytes_to_insert] IN new_corrector;
      FOR j := 1 TO new_item^.bytes_to_insert DO
        new_bytes^ [j] := save_bytes^ [j];
      FOREND;
      new_corrector_header^.size := new_corrector_header^.size + #SIZE (new_bytes^);
    IFEND;
    new_corrector_header^.number_of_correctors := new_corrector_header^.number_of_correctors + 1;
    FREE save_bytes;

  PROCEND compress_corrector;
?? OLDTITLE ??
?? NEWTITLE := 'tune_search_options', EJECT ??

  PROCEDURE tune_search_options
    (    breaklist_kind: oct$breaklist_kind;
     VAR length: oct$breaklist_length;
     VAR max_tries: oct$breaklist_index);


    length := 2;
    max_tries := 100;
    CASE breaklist_kind OF
    = occ$code =
      length := 8;
      max_tries := 500;
    = occ$idr =
      length := 2;
      max_tries := 50;
    = occ$read =
      length := 20;
      max_tries := 148;
    = occ$ept =
      length := 2;
      max_tries := 55;
    = occ$ext =
      length := 2;
      max_tries := 55;
    = occ$module_header =
      length := 8;
      max_tries := 100;
    = occ$adr =
      length := 4;
      max_tries := 100;
    = occ$text =
      length := 2;
      max_tries := 50;
    = occ$secdef =
      length := 8;
      max_tries := 64;
    = occ$object_library_header =
      length := 2;
      max_tries := 50;
    = occ$dictionary =
      length := 27;
      max_tries := 148;
    = occ$command_proc =
      length := 4;
      max_tries := 30;
    = occ$program_des =
      length := 4;
      max_tries := 30;
    = occ$app_command_proc =
      length := 4;
      max_tries := 30;
    = occ$app_program_des =
      length := 4;
      max_tries := 30;
    = occ$info_element =
      length := 8;
      max_tries := 30;
    = occ$bti =
      length := 8;
      max_tries := 148;
    = occ$rel =
      length := 4;
      max_tries := 10;
    = occ$component =
      length := 8;
      max_tries := 24;
    = occ$section_map =
      length := 2;
      max_tries := 19;
    = occ$function_proc =
      length := 2;
      max_tries := 100;
    = occ$message_mod =
      length := 2;
      max_tries := 100;
    = occ$panel_mod =
      length := 2;
      max_tries := 100;
    = occ$library_member_header =
      length := 2;
      max_tries := 100;
    = occ$mtm_header =
      length := 2;
      max_tries := 100;
    = occ$mtm_cc =
      length := 2;
      max_tries := 100;
    = occ$mtm_cn =
      length := 2;
      max_tries := 100;
    = occ$mess_temp =
      length := 2;
      max_tries := 100;
    = occ$m68000 =
      length := 4;
      max_tries := 500;
    = occ$deferred_ept =
      length := 2;
      max_tries := 56;
    = occ$deferred_common_blk =
      length := 2;
      max_tries := 73;
    ELSE
      ;
    CASEND;
  PROCEND tune_search_options;
?? OLDTITLE ??
?? NEWTITLE := 'search_until_match', EJECT ??

{ PURPOSE:
{    The purpose of this request is to search the bytes in a breaklist item
{ until a match is found.  The number of bytes between where the search is
{ started and where the match is found determines the number of bytes to be
{ inserted or deleted.

  PROCEDURE search_until_match
    (    breaklist_kind: oct$breaklist_kind;
         old_breaklist_bytes: ^byte_array;
         new_breaklist_bytes: ^byte_array;
         old_start: oct$breaklist_length;
         new_start: oct$breaklist_length;
     VAR bytes_to_delete: oct$offset;
     VAR bytes_to_insert: oct$offset);

    VAR
      compare_length: oct$breaklist_length,
      i: oct$breaklist_length,
      match: boolean,
      max_tries: oct$breaklist_index,
      new: oct$breaklist_length,
      new_length: oct$breaklist_length,
      new_remainder: oct$breaklist_length,
      old: oct$breaklist_length,
      old_length: oct$breaklist_length,
      old_remainder: oct$breaklist_length;

{ This procedure searches through the old and new object libraries using
{ arrays which point to certain sections of the object library.  The
{ variables used to accomplish this search are -
{       OLD_LENGTH:  The length (or last byte) of the old breaklist item array.
{       NEW_LENGTH:  The length (or last byte) of the new breaklist item array.
{       OLD_START:   The position in the old breaklist of the first byte compared
{                    during this call of the procedure.
{       NEW_START:   The position in the new breaklist of the first byte compared
{                    during this call of the procedure.
{       OLD:         The current position in the old breaklist array.
{       NEW:         The current position in the new breaklist array.
{ OLD_START, NEW_START, OLD_LENGTH, and NEW_LENGTH are not changed during the running
{ of this procedure.  OLD and NEW are updated as needed to make comparisons while searching.

    old_length := UPPERBOUND (old_breaklist_bytes^);
    new_length := UPPERBOUND (new_breaklist_bytes^);
    old := old_start;
    new := new_start + 1;

{ The call to OCP$TUNE_SEARCH_OPTIONS sets initial values for COMPARE_LENGTH and
{ MAX_TRIES which are used in searching.  These variables are tuned to the kind
{ of breaklist being searched.  (For example: CODE, RELOCATION RECORDS, BINDING
{ TEMPLATE RECORDS, SECTION_MAPS, etc.) COMPARE_LENGTH is the number of consecutive
{ bytes that must be the same in order to be considered a match.  MAX_TRIES is
{ the number of compares that may be made before "giving up" and beginning to
{ look for a match at the next possible location.  As the search approaches the
{ end of the breaklist item, both MAX_TRIES and COMPARE_LENGTH will be updated
{ to allow searching to continue right to the end of the breaklist item.

    tune_search_options (breaklist_kind, compare_length, max_tries);

    match := FALSE;
    WHILE NOT match AND (old <= old_length) AND (new <= new_length) DO

{ As mentioned above,  MAX_TRIES and COMPARE_LENGTH get updated when getting
{ to the end of a breaklist item.  If COMPARE_LENGTH is greater than the
{ number of bytes left in the old or the new breaklist it is adjusted to the
{ smaller of the two.  If MAX_TRIES is greater than the number of bytes left in
{ the new breaklist it gets reset to this number.  These adjustments are made in
{ order to allow comparisons to be made right to the end of the breaklist item.

      old_remainder := old_length - old + 1;
      new_remainder := new_length - new + 1;
      IF (old_remainder < compare_length) AND (old_remainder <= new_remainder) THEN
        compare_length := old_remainder;
      ELSEIF (new_remainder < compare_length) AND (new_remainder <= old_remainder) THEN
        compare_length := new_remainder;
      IFEND;

      IF max_tries > (new_length - new + 1) THEN
        max_tries := new_length - new + 1;
      IFEND;

{ Check for a match that is the appropriate number of bytes long.

      match := TRUE;
      i := 0;
      WHILE match AND (i < compare_length) DO
        match := (old_breaklist_bytes^ [old + i] = new_breaklist_bytes^ [new + i]);
        i := i + 1;
      WHILEND;

      IF match THEN
        bytes_to_delete := old - old_start;
        bytes_to_insert := new - new_start;

      ELSEIF ((new - new_start + 1) >= max_tries) AND ((old - old_start) <= (max_tries * 2)) THEN

{ If enough compares have been made between one byte item (a byte item would be a string of bytes
{ the length of COMPARE_LENGTH) in the old breaklist and MAX_TRIES byte items in the new breaklist,
{ restart the search at the next byte item in the old breaklist and the starting location in the new
{ breaklist.

        old := old + 1;
        new := new_start;

      ELSEIF ((new - new_start + 1) >= max_tries) AND ((old - old_start) > (max_tries * 2)) THEN

{ If enough compares have been made in searching for a match (MAX_TRIES * 2 byte items in the
{ old breaklist with MAX_TRIES byte items in the new breaklist), then "give up".  Although a match
{ has not actually been found, this flag is set in order to drop out of the loop.  Bytes_to_delete
{ and bytes_to_insert are set to delete MAX_TRIES bytes from the old breaklist and to insert
{ MAX_TRIES bytes in the new breaklist since no match was found.  The next time this procedure is
{ entered OLD_START and NEW_START will be set so that searching will begin again at this point.

        match := TRUE;
        old := old_start + max_tries;
        bytes_to_delete := old - old_start;
        bytes_to_insert := new - new_start;

      ELSE

{ If a match is not found continue searching with the next byte item in the new breaklist.

        new := new + 1;
      IFEND;
    WHILEND;

    IF (old > old_length) OR (new > new_length) THEN
      bytes_to_delete := old_length - old_start + 1;
      bytes_to_insert := new_length - new_start + 1;
    IFEND;
  PROCEND search_until_match;
?? OLDTITLE ??
?? NEWTITLE := 'search_until_difference', EJECT ??

{ PURPOSE:
{    The purpose of this request is to compare bytes in an old breaklist
{ and a new breaklist and count the bytes that match until a non-match is
{ found.

  PROCEDURE search_until_difference
    (    old_breaklist_bytes: ^byte_array;
         new_breaklist_bytes: ^byte_array;
         old_start: oct$breaklist_length;
         new_start: oct$breaklist_length;
     VAR bytes_ok: oct$offset);

    VAR
      match: boolean,
      new: oct$breaklist_length,
      new_length: oct$breaklist_length,
      old: oct$breaklist_length,
      old_length: oct$breaklist_length;

    old_length := UPPERBOUND (old_breaklist_bytes^);
    new_length := UPPERBOUND (new_breaklist_bytes^);
    old := old_start;
    new := new_start;
    match := TRUE;
    WHILE match AND (old <= old_length) AND (new <= new_length) DO
      match := (old_breaklist_bytes^ [old] = new_breaklist_bytes^ [new]);
      old := old + 1;
      new := new + 1;
    WHILEND;

    IF match THEN
      bytes_ok := old - old_start;
    ELSE
      bytes_ok := old - old_start - 1;
    IFEND;
  PROCEND search_until_difference;
?? OLDTITLE ??
?? NEWTITLE := 'build_corrector_item', EJECT ??

{ PURPOSE:
{    The purpose of this request is to build an individual corrector item,
{ which together with the corrector header and the bytes that must be inserted,
{ make up the entire corrector which is used in the third and final pass of
{ an update.
{    Each corrector item specifies the number of bytes that are ok (can be
{ left unchanged from old version to new version), the number of bytes
{ to delete from the old version and the number of bytes to be inserted.
{ If there are bytes to insert they are placed in the corrector right after
{ the corrector item that references them.

  PROCEDURE build_corrector_item
    (    bytes_ok: oct$offset;
         bytes_to_delete: oct$offset;
         bytes_to_insert: oct$offset;
         from: ^cell;
     VAR corrector: ^SEQ ( * );
     VAR corrector_header: ^oct$corrector_header);

    VAR
      corrector_item: ^oct$corrector_item,
      new_bytes: ^oct$new_bytes;

    NEXT corrector_item IN corrector;
    corrector_item^.bytes_ok := bytes_ok;
    corrector_item^.bytes_to_delete := bytes_to_delete;
    corrector_item^.bytes_to_insert := bytes_to_insert;
    corrector_header^.size := corrector_header^.size + #SIZE (corrector_item^);

    IF bytes_to_insert > 0 THEN
      NEXT new_bytes: [1 .. bytes_to_insert] IN corrector;
      i#move (from, new_bytes, bytes_to_insert);
      corrector_header^.size := corrector_header^.size + bytes_to_insert;
    IFEND;

    corrector_header^.number_of_correctors := corrector_header^.number_of_correctors + 1;
  PROCEND build_corrector_item;
?? OLDTITLE ??

MODEND ocm$build_corrector;
