(* gm2lorder.mod ensure that underlying runtime modules are initialized.

Copyright (C) 2008-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Modula-2; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  *)

MODULE gm2lorder ;

(*
   Author     : Gaius Mulley
   Title      : gm2lorder
   Date       : Thu Sep  4 21:18:33 BST 2008
   Description: Ensures that underlying runtime modules are initialized
                before all other modules.
*)


FROM libc IMPORT exit ;
FROM ASCII IMPORT eof ;
FROM SArgs IMPORT GetArg ;
FROM StrLib IMPORT StrLen ;
FROM DynamicStrings IMPORT String, InitString, KillString, Length, Equal, EqualArray, Slice, Mark ;
FROM Indexing IMPORT Index, PutIndice, GetIndice, RemoveIndiceFromIndex, InitIndex, KillIndex, HighIndice ;
FROM FIO IMPORT File, StdIn, StdOut, StdErr, WriteChar, WriteLine,
                Close, EOF, IsNoError, WriteString, FlushBuffer ;
FROM SFIO IMPORT OpenToRead, OpenToWrite, ReadS, WriteS ;

IMPORT DynamicStrings ;


CONST
   Comment = '#' ;               (* Comment identifier.     *)
   DefaultRuntimeModules = 'Storage,SYSTEM,M2RTS,RTExceptions,IOLink' ;

VAR
   fi, fo    : File ;
   runTime   : Index ;
   moduleList: Index ;


(*
   InitRuntimeModules - initializes the list of critical runtime modules
                        which must be initialized first and in a particular
                        order.
*)

PROCEDURE InitRuntimeModules (s: String) ;
VAR
   a   : CARDINAL ;
   i, j: INTEGER ;
BEGIN
   IF runTime # NIL
   THEN
      runTime := KillIndex (runTime)
   END ;
   runTime := InitIndex (0) ;
   i := 0 ;
   a := 0 ;
   REPEAT
      j := DynamicStrings.Index (s, ',', i) ;
      IF j = -1
      THEN
         PutIndice (runTime, a, Slice (s, i, 0))
      ELSE
         PutIndice (runTime, a, Slice (s, i, j)) ;
         i := j+1
      END ;
      INC(a)
   UNTIL j=-1 ;
   s := KillString (s)
END InitRuntimeModules ;


(*
   Reorder - reorders the list of modules to ensure critical runtime
             modules are initialized first.  It writes out the new
             ordered list.
*)

PROCEDURE Reorder ;
VAR
   rh, mh,
   ri, mi: CARDINAL ;
   rs, ms: String ;
BEGIN
   rh := HighIndice (runTime) ;
   mh := HighIndice (moduleList) ;
   ri := 0 ;
   WHILE ri <= rh DO
      mi := 0 ;
      rs := GetIndice (runTime, ri) ;
      WHILE mi <= mh DO
         ms := GetIndice (moduleList, mi) ;
         IF Equal (rs, ms)
         THEN
            rs := WriteS (fo, rs) ; WriteLine (fo) ;
            RemoveIndiceFromIndex (moduleList, ms) ;
            mh := HighIndice (moduleList)
         ELSE
            INC (mi)
         END
      END ;
      INC (ri)
   END ;
   mi := 0 ;
   WHILE mi <= mh DO
      ms := GetIndice (moduleList, mi) ;
      ms := WriteS (fo, ms) ; WriteLine (fo) ;
      INC (mi)
   END ;
   Close (fo)
END Reorder ;


(*
   ReadList - populates the moduleList with a list of module names.
*)

PROCEDURE ReadList ;
VAR
   s: String ;
   i: CARDINAL ;
BEGIN
   moduleList := InitIndex (0) ;
   i := 0 ;
   s := ReadS (fi) ;
   WHILE NOT EOF (fi) DO
      IF NOT EqualArray (s, '')
      THEN
         PutIndice (moduleList, i, s) ;
         INC (i)
      END ;
      s := ReadS (fi)
   END ;
   IF NOT EqualArray (s, '')
   THEN
      PutIndice (moduleList, i, s)
   END
END ReadList ;


(*
   OpenOutputFile - attempts to open an output file.
*)

PROCEDURE OpenOutputFile (s: String) ;
BEGIN
   IF EqualArray(s, '-')
   THEN
      fo := StdOut
   ELSE
      fo := OpenToWrite(s) ;
      IF NOT IsNoError(fo)
      THEN
         WriteString(StdErr, 'cannot write to: ') ; s := WriteS(StdErr, s) ; WriteLine(StdErr) ;
         exit(1)
      END
   END
END OpenOutputFile ;


(*
   Usage - prints out a usage and exits with 0.
*)

PROCEDURE Usage ;
BEGIN
   WriteString(StdOut, 'gm2lorder [-h] [-o outputfile] [-fruntime-modules=] inputfile') ; WriteLine(StdOut) ;
   WriteString(StdOut, '    inputfile is a file containing a list of modules, each module on a separate line') ; WriteLine(StdOut) ;
   WriteString(StdOut, '    the list of runtime modules can be specified as follows -fruntime-modules=module1,module2,module3') ; WriteLine(StdOut) ;
   WriteString(StdOut, '    the default for this flag is -fruntime-modules=') ;
   WriteString(StdOut, DefaultRuntimeModules) ; WriteLine(StdOut) ;
   WriteString(StdOut, '    Note that the list of runtime modules does not mean they will appear in the executable') ; WriteLine(StdOut) ;
   WriteString(StdOut, '    a runtime module is only included into the final executable if it is required,') ; WriteLine(StdOut) ;
   WriteString(StdOut, '    however gm2lorder will ensure the order of these modules.') ; WriteLine(StdOut) ;
   FlushBuffer(StdOut) ;
   exit(0)
END Usage ;


(*
   ScanArgs - scans arguments.
*)

PROCEDURE ScanArgs ;
VAR
   i        : CARDINAL ;
   s        : String ;
   FoundFile: BOOLEAN ;
BEGIN
   FoundFile := FALSE ;
   fi        := StdIn ;
   fo        := StdOut ;
   i := 1 ;
   WHILE GetArg(s, i) DO
      IF EqualArray(s, '-o')
      THEN
         s := KillString(s) ;
         INC(i) ;
         IF GetArg(s, i)
         THEN
            OpenOutputFile(s)
         ELSE
            WriteString(StdErr, 'missing filename option after -o') ; WriteLine(StdErr) ;
            exit(1)
         END
      ELSIF EqualArray(s, '-h')
      THEN
         Usage
      ELSIF EqualArray(Mark(Slice(s, 0, StrLen('-fruntime-modules='))), '-fruntime-modules=')
      THEN
         InitRuntimeModules(Slice(s, StrLen('-fruntime-modules='), 0))
      ELSE
         IF FoundFile
         THEN
            WriteString(StdErr, 'already opened one file for reading') ; WriteLine(StdErr)
         ELSE
            FoundFile := TRUE ;
            fi := OpenToRead(s) ;
            IF NOT IsNoError(fi)
            THEN
               WriteString(StdErr, 'failed to open: ') ; s := WriteS(StdErr, s) ; WriteLine(StdErr) ;
               exit(1)
            END
         END
      END ;
      INC(i)
   END ;
   IF NOT FoundFile
   THEN
      WriteString(StdErr, 'a module file list must be specified on the command line') ; WriteLine(StdErr) ;
      exit(1)
   END
END ScanArgs ;


(*
   Init -
*)

PROCEDURE Init ;
BEGIN
   runTime := NIL ;
   moduleList := NIL ;
   InitRuntimeModules(InitString(DefaultRuntimeModules)) ;
   ScanArgs ;
   ReadList ;
   Reorder
END Init ;


BEGIN
   Init
END gm2lorder.