Skip to content

Execute as a script with mutecompiler#275

Open
dn2007hw wants to merge 17 commits intosmlnj:mainfrom
dn2007hw:dn2007hw-patch-2-with-mutecompiler
Open

Execute as a script with mutecompiler#275
dn2007hw wants to merge 17 commits intosmlnj:mainfrom
dn2007hw:dn2007hw-patch-2-with-mutecompiler

Conversation

@dn2007hw
Copy link

@dn2007hw dn2007hw commented Apr 15, 2023

Script execution made simple with SML/NJ

Submitted By: Dayanandan Natarajan
Supervisor: Joe Wells
School of Mathematical and Computer Sciences
Heriot-Watt University

Objective

Primary objective of this change is to give the programmers and developers the capability of running a SML program as a script. Also the capability to mute and unmute compiler messages. Programmers and developers can write a SML program and run the program like a script over the command prompt.

Files modified

smlnj/base/cm/main/cm-boot.sml
smlnj/base/system/smlnj/internal/boot-env-fn.sml
smlnj/base/compiler/TopLevel/interact/interact.sig
smlnj/base/compiler/TopLevel/interact/interact.sml
smlnj/base/compiler/TopLevel/backend/backend-fn.sml
smlnj/base/compiler/TopLevel/backend/backend.sig
smlnj/base/compiler/TopLevel/interact/mutecompiler.sig (New component)
smlnj/base/compiler/TopLevel/interact/mutecompiler.sml (New component)
smlnj/base/compiler/INDEX
smlnj/base/compiler/MAP
smlnj/base/compiler/core.cm

Change details

  1. interact.sig & interact.sml

A new function (useScriptFile) is added to Backend.Interact structure, which takes the file name and its content as a stream and process the stream by passing it to EvalLoop.evalStream. The compiler messages are muted and unmuted before the processing of the file. (Functions silenceCompiler, dummyfn and unsilenceCompiler will be explained later in this document)

a) New function declaration is added to interact.sig,

val useStream : TextIO.instream -> unit
val useScriptFile : string * TextIO.instream -> unit (* Addded by DAYA *)
val evalStream : TextIO.instream * Environment.environment -> Environment.environment

b) New function definition is added to interact.sml,

fun useScriptFile (fname, stream) = ( 
  
  Mutecompiler.silenceCompiler () ;
  EvalLoop.evalStream ("<instream>", (TextIO.openString "Backend.Mutecompiler.mcdummyfn ();") ) ;
  Mutecompiler.unsilenceCompiler () ;

  (EvalLoop.evalStream (fname, stream))
    handle exn => ( 
      Mutecompiler.printStashedCompilerOutput ();
      Mutecompiler.unsilenceCompiler ();
      EvalLoop.uncaughtExnMessage exn
      )  
  )
  1. cm-boot.sml

The following changes were made in cm-boot.sml to recognise the new command line parameter passed from script.

a) In function args, line added to recognise the new command-line parameter ‘--script’, and a new function ‘nextargscript’ is called to initiate the process of the file.

    fun args ([], _) = ()
      | args ("-a" :: _, _) = nextarg autoload'
      | args ("-m" :: _, _) = nextarg make'
      | args (["-H"], _) = (help NONE; quit ())
      | args ("-H" :: _ :: _, mk) = (help NONE; nextarg mk)
      | args (["-S"], _) = (showcur NONE; quit ())
      | args ("-S" :: _ :: _, mk) = (showcur NONE; nextarg mk)
      | args (["-E"], _) = (show_envvars NONE; quit ())
      | args ("-E" :: _ :: _, mk) = (show_envvars NONE; nextarg mk)
      | args ("--script" :: _, _) = (nextargscript ())  (* line added by DAYA *)
      | args ("@CMbuild" :: rest, _) = mlbuild rest
      | args (["@CMredump", heapfile], _) = redump_heap heapfile
      | args (f :: rest, mk) =
      (carg (String.substring (f, 0, 2)
         handle General.Subscript => "",
         f, mk, List.null rest);
       nextarg mk)

    and nextarg mk =
    let val l = SMLofNJ.getArgs ()
    in SMLofNJ.shiftArgs (); args (l, mk)
    end

    (* nextargscript added by DAYA *)
    and nextargscript () =
    let val l = SMLofNJ.getArgs ()
    in SMLofNJ.shiftArgs (); processFileScript (hd l); quit ()
    end

b) In function init(), the new function (useScriptFile) is added as one of the parameter passed,

fun init (bootdir, de, er, useStream, useScriptFile, useFile, errorwrap, icm) = let

c) In function procCmdLine (), new function processFileScript is added to process the script file, function will check for whether the file passed on is a script file starting with ‘#!’ thru another new function checkSharpbang, consumes the first line thru another new function eatuntilneline and pass the remaining content of the file to function useScriptFile.

      (* DAYA change starts here *)
        fun eatuntilnewline (instream : TextIO.instream): bool = let
            val c = TextIO.input1 instream
            in
                case TextIO.lookahead instream of
                    SOME #"\n" => true
                    | SOME c => eatuntilnewline instream
                    | NONE => false
            end

        fun checkSharpbang (instream : TextIO.instream): bool = let
            val c = TextIO.input1 instream
            in
                case c of
                    SOME #"#" => (
                        case TextIO.lookahead instream of
                            SOME #"!" => eatuntilnewline instream
                            | SOME c => false
                            | NONE => false
                            )
                    | SOME c => false
                    | NONE => false
            end

        fun processFileScript (fname) = let
            val stream = TextIO.openIn fname
            val isscript = checkSharpbang stream
            in
                if (isscript) = false  
                then    ( Say.say [ "!* Script file doesn't start with #!. \n" ] ) 
                else    ( useScriptFile (fname, stream) )
            end
        (* DAYA change ends here *)
  1. boot-env-fn.sml

In functor BootEnvF, cminit function declaration is amended to include the newly added function useScriptFile.

functor BootEnvF (datatype envrequest = AUTOLOAD | BARE
val architecture: string
val cminit : string * DynamicEnv.env * envrequest
* (TextIO.instream -> unit)(* useStream )
* (string * TextIO.instream -> unit) (
useScriptFile )
* (string -> unit) (
useFile )
* ((string -> unit) -> (string -> unit))
(
errorwrap *)
* ({ manageImport:
Ast.dec * EnvRef.envref -> unit,
managePrint:
Symbol.symbol * EnvRef.envref -> unit,
getPending : unit -> Symbol.symbol list }
-> unit)
-> (unit -> unit) option
val cmbmake: string * bool -> unit) :> BOOTENV = struct

  1. backend.sig
    A new structure Mutecompiler is declared within signature BACKEND,

signature BACKEND = sig
structure Profile : PROFILE
structure Compile : COMPILE
structure Interact : INTERACT
structure Mutecompiler : MUTECOMPILER
structure Machine : MACHINE
val architecture: string
val abi_variant: string option
end

  1. backend-fn.sml
    New structure Mutecompiler is defined within functor BackendFn,

structure Mutecompiler = Mutecompiler

  1. mutecompiler.sig
    New signature MUTECOMPILER is defined with all the global variables and functions that are part of Structure Mutecompiler,

  2. mutecompiler.sml
    New structure Mutecompiler has the following core functions,

a. silencecompiler function mutes the compiler messages by saving the current printing limits in a ref cell, then set them all to zero and stashes the compiler messages by saving the value of Control.Print.out in a ref cell.

b. unsilencecompiler function unmutes the compiler messages by restoring the printing limits and value of Control.Print.out from stored ref cell.

c. printStashedCompilerOutput function prints the stashed compiler messages to the user.

d. dummyfn function which does nothing is created and invoked to preload the Mutecompiler structure before the script is passed to evalloop, this is to supress the structure auto-loading logs in the script results.

  1. INDEX, MAP and core.cm
    INDEX, MAP and core.cm are updated with definitions for signature MUTECOMPILER and structure Mutecompiler.

a. INDEX
MUTECOMPILER
TopLevel/interact/mutecompiler.sig
Mutecompiler
TopLevel/interact/mutecompiler.sml

b. MAP
interact/
envref.sml
supports top-level environment management
defs: ENVREF, EnvRef : ENVREF
evalloop.sig,sml
top-level read-eval-print loop
defs: EVALLOOP, EvalLoopF: TOP_COMPILE => EVALLOOP
interact.sig,sml
creating top-level loops
defs: INTERACT, Interact: EVALLOOP => INTERACT
mutecompiler.sig,sml
allow compiler silencing
defs: MUTECOMPILER, Mutecompiler

c. core.cm
TopLevel/interact/mutecompiler.sig
TopLevel/interact/mutecompiler.sml

Writing a script

The script should start with ‘#!’ in the first line followed by the environment location, command-line parameters ‘-Ssml’ and ‘—script’, a new line and then followed by the SML code or program.

Example script named ‘sample’,
--------------beginning of the script-------------
#!/usr/bin/env -Ssml –script
;(--SML--)
val () = print "Hello World\n";
--------------end of the script---------------------

Running a script

The script ‘sample’ can be executed from Linux terminal or command prompt as a regular OS script as below provided it is given execution permission,

$ ./sample

Additional functions

a. The compiler messages can be muted/suppressed by invoking the silencecompiler function as below in the script,

val _ = Backend.Mutecompiler.silenceCompiler ();

b. The compiler messages can be unmuted by invoking the unsilencecompiler function as below in the script,

val _ = Backend.Mutecompiler.unsilenceCompiler ();

c. Whenever an error is encountered in compiling, by default only last 5 lines of the suppressed compiler messages are printed to the user and this limit can be pre-set in script as below,

Backend.Mutecompiler.printlineLimit := 10;

d. Whenever compiler messages are muted by calling silenceCompiler function, variable declarations are stashed with ‘#’ in suppressed compiler messages to save memory. To see the original content in case of debugging, this can be retrieved by amending the Control print parameters and restoring the print limits by increasing the string depth and calling the restorePrintingLimits function as below in the script,

Control.Print.stringDepth := 999;
val _ = Backend.Mutecompiler.restorePrintingLimits ();

SML/NJ Version used

Our development and testing is based on Standard ML of New Jersey (32-bit) v110.99.3 on an Intel based macOS 10.13.16.

Test Details

Test Script #1

#!/usr/bin/env -Ssml --script
;(* SML code starts here *)
val x = "Hello World x\n";
val () = print x;
val y = "Hello World y\n";
val () = print y;
val z = "Hello World z\n";
val () = print z;

Test Result #1

$ ./exml07
Standard ML of New Jersey (32-bit) v110.99.3 [built: Mon Apr 10 18:03:19 2023]
val x = "Hello World x\n" : string
Hello World x
val y = "Hello World y\n" : string
Hello World y
val z = "Hello World z\n" : string
Hello World z
$

Test Script #2

#!/usr/bin/env -Ssml --script
;(* SML code starts here *)
val _ = Backend.Mutecompiler.silenceCompiler ();
val x = "Hello World x\n";
val () = print x;
val y = "Hello World y\n";
val () = print y;
val z = "Hello World z\n";
val () = print z;

Test Result #2

$ ./exml07
Standard ML of New Jersey (32-bit) v110.99.3 [built: Mon Apr 10 18:03:19 2023]
Hello World x
Hello World y
Hello World z
$

Test Script #3

#!/usr/bin/env -Ssml --script
;(--SML--)
val x = "Hello World x\n";
val () = print x;
val _ = Backend.Mutecompiler.silenceCompiler ();
val y = "Hello World y\n";
val () = print y;
val _ = Backend.Mutecompiler.unsilenceCompiler ();
val z = "Hello World z\n";
val () = print z;

Test Result #3

$ ./sample
Standard ML of New Jersey (32-bit) v110.99.3 [built: Mon Apr 10 18:03:19 2023]
val x = "Hello World x\n" : string
Hello World x
Hello World y
val z = "Hello World z\n" : string
Hello World z
$

Test Script #4

#!/usr/bin/env -Ssml --script
;(--SML--)
val x = "Hello World x\n";
val () = print x;
val _ = Backend.Mutecompiler.silenceCompiler ();
Control.Print.stringDepth := 999;
val _ = Backend.Mutecompiler.restorePrintingLimits ();
val y = "Hello World y\n";
val () == print y;
val _ = Backend.Mutecompiler.unsilenceCompiler ();
val z = "Hello World z\n";
val () = print z;

Test Result #4
$ ./sample
Standard ML of New Jersey (32-bit) v110.99.3 [built: Mon Apr 10 18:03:19 2023]
val x = "Hello World x\n" : string
Hello World x


The last 5 lines 31 through 35 of suppressed compiler messages are:
[library $SMLNJ-MLRISC/IA32.cm is stable]
[autoloading done]
val it = # : unit
val y = "Hello World y\n" : string
./exml07:8.18-9.4 Error: syntax error: deleting SEMICOLON VAL
End of suppressed compiler messages._____

uncaught exception Compile [Compile: "syntax error"]
raised at: ../compiler/Parse/main/smlfile.sml:19.24-19.46
../compiler/TopLevel/interact/evalloop.sml:45.54
../compiler/TopLevel/interact/evalloop.sml:306.20-306.23$

dn2007hw added 11 commits April 15, 2023 17:17
Changes made in cm-boot.sml to recognise the new command line parameter passed from script.

a)	In function args, line added to recognise the new command-line parameter ‘--script’, and a new function ‘nextargscript’ is called to initiate the process of the file.
b)	In function init(), the new function (useScriptFile) is added as one of the parameter passed.
c)	In function procCmdLine (), new function processFileScript is added to process the script file, function will check for whether the file passed on is a script file starting with ‘#!’ thru another new function checkSharpbang, consumes the first line thru another new function eatuntilneline and pass the remaining content of the file to function useScriptFile.
In functor BootEnvF, cminit function declaration is amended to include the newly added function useScriptFile.
A new function (useScriptFile) is added to Backend.Interact structure, which takes the file name and its content as a stream and process the stream by passing it to EvalLoop.evalStream. The compiler messages are muted and unmuted before the processing of the file.

a)	New function declaration is added to interact.sig,
b)	New function definition is added to interact.sml,
New signature MUTECOMPILER is defined with all the global variables and functions that are part of Structure Mutecompiler,
New structure Mutecompiler has two core functions silencecompiler and unsilencecompier.
a.	silencecompiler function mutes the compiler messages by saving the current printing limits in a ref cell and then set them all to zero.
b.	unsilencecompiler function unmutes the compiler messages by restoring the printing limits.
c.	dummyfn function which does nothing is created and invoked to preload the Mutecompiler structure before the script is passed to evalloop, this is to supress the structure auto-loading logs in the script results.
A new structure Mutecompiler is declared within signature BACKEND.
New structure Mutecompiler is defined within functor BackendFn.
INDEX, MAP and core.cm are updated with definitions for signature MUTECOMPILER and structure Mutecompiler.
@dn2007hw dn2007hw changed the title Dn2007hw patch 2 with mutecompiler Execute as a script with mutecompiler Apr 15, 2023
@dn2007hw dn2007hw marked this pull request as ready for review April 17, 2023 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant