smlnj / legacy

This project is the old version of Standard ML of New Jersey that continues to support older systems (e.g., 32-bit machines).
BSD 3-Clause "New" or "Revised" License
25 stars 10 forks source link

Execute as a script with mutecompiler #275

Open dn2007hw opened 1 year ago

dn2007hw commented 1 year ago

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

  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$