The PolyML structure

Generally, the basis library of Poly/ML follows the ML standard libraries. With a few exceptions the extensions are all contained in the PolyML structure. There are a number of sub-structures that are documented separately.

structure PolyML:
sig
   type location =
       {file: string, startLine: int, endLine: int, startPosition: int, endPosition: int}

   structure CodeTree : sig ... end

   structure Compiler : sig ... end 

   val compiler: (unit -> char option) * Compiler.compilerParameters list -> unit -> unit

   structure Debug : sig ... end

   structure DebuggerInterface : sig ... end

   structure Exception : sig ... end
   val exceptionLocation: exn -> location option
   val raiseWithLocation : exn * location -> 'a
   val exception_trace: (unit -> 'a) -> 'a

   structure NameSpace : sig ... end
   val globalNameSpace: NameSpace.nameSpace

   structure Profiling: sig ... end

   structure SaveState : sig ... end
   val loadModule: string -> unit

   structure Statistics : sig ... end

   structure IDEInterface :
   sig
      val parseTree : (string * (location * ptProperties list) list) ref
      val runIDEProtocol : unit -> unit
   end

   structure IntInf:
   sig
      val gcd : int * int -> int
      val lcm : int * int -> int
   end

   val export: string * (unit -> unit) -> unit
   val exportPortable: string * (unit -> unit) -> unit
   val shareCommonData : 'a -> unit

   val onEntry : (unit -> unit) -> unit

   val architecture : unit -> string
   val rtsArgumentHelp : unit -> string
   val rtsVersion : unit -> int

   val make: string -> unit
   val use : string -> unit
   val getUseFileName: unit -> string option
   val suffixes = ref ["", ".ML", ".sml"]: string list ref

   val objSize: 'a -> int
   val showSize : 'a -> int
   val objProfile : 'a -> int

   datatype ptProperties =
        PTbreakPoint of bool ref
| PTcompletions of string list
| PTdeclaredAt of location
| PTdefId of int
| PTfirstChild of unit -> parseTree
| PTnextSibling of unit -> parseTree
| PTopenedAt of location
| PTparent of unit -> parseTree
| PTpreviousSibling of unit -> parseTree
| PTprint of int -> pretty
| PTreferences of bool * location list
| PTrefId of int
| PTstructureAt of location
| PTtype of NameSpace.Values.typeExpression type parseTree = location * ptProperties list datatype context = ContextLocation of location | ContextProperty of string * string datatype pretty = PrettyBlock of int * bool * context list * pretty list | PrettyBreak of int * int | PrettyLineBreak | PrettyString of string | PrettyStringWithWidth of string * int val prettyPrint : (string -> unit) * int -> pretty -> unit val prettyMarkup : (context list -> unit) * (context list -> unit) -> (string -> unit) * int -> pretty -> unit val prettyPrintWithIDEMarkup : (string -> unit) * int -> pretty -> unit val addPrettyPrinter : (int -> 'a -> 'b -> pretty) -> unit val prettyRepresentation : 'a * int -> pretty val print: 'a -> 'a val makestring: 'a -> string val print_depth: int -> unit val error_depth: int -> unit val line_length: int -> unit val rootFunction : unit -> unit val shell : unit -> unit val sourceLocation : unit -> location val fullGC : unit -> unit val pointerEq : 'a * 'a -> bool val stackTrace : unit -> unit val profiling : int -> unit val timing : bool -> unit end

The PolyML structure contains a large collection of functions, structures and types that are specific to Poly/ML.

structure CodeTree : sig ... end 

Functions related to the intermediate code structure.

structure Compiler : sig ... end

Functions and types specific to the Poly/ML compiler.

val compiler: (unit -> char option) * Compiler.compilerParameters list -> unit -> unit

The PolyML.compiler function provides direct access to the compiler for Poly/ML. It is used by all the functions, such as use, shell and make that compile Standard ML source code into executable code.

compiler(instream, parms) takes an input stream, instream, and a set of optional parameters, parms, of type Compiler.compilerParameters. The input stream is called whenever the compiler wants to read the next character from the input. If it returns NONE the compiler assumes that it has reached an end-of-stream and does not read further. Otherwise the compiler stops when it has parsed a valid topdec, generally a declaration or expression with a final semicolon. The parameters, parms, provide a set of options to the compiler. There are default values for these options so it is possible to provide an empty list. The parameters, their meanings and the default values are listed under Compiler.compilerParameters.

If an error is detected compiler raises the Fail exception, otherwise the result of the compilation is a function of type unit->unit. This function represents the compiled code and calling this function executes the code. At that point any side-effects will be performed and bindings made by the code will be added to the name-space.

The compiler function itself is thread-safe, that is multiple instances of it can be executed in parallel without interference, provided suitable parameters are provided. In particular it may be necessary to ensure that the name-spaces passed as CPNameSpace parameters do not interfere.

structure Debug : sig ... end

Functions to aid interactive debugging. There is a reference here and a tutorial here.

structure DebuggerInterface : sig ... end

The DebuggerInterface structure contains functions to allow application code to access information about a program that has been compiled for debugging. It is intended for applications that replace the default Poly/ML top-level.

structure Exception : sig ... end
val exceptionLocation: exn -> location option
val raiseWithLocation : exn * location -> 'a
val exception_trace: (unit -> 'a) -> 'a

The Exception structure contains functions to assist with the location and tracing of exceptions. Three functions, exception_trace, exceptionLocation and raiseWithLocation were originally contained in the PolyML structure itself so for backwards compatibility they are available both in PolyML and PolyML.Exception.

structure NameSpace : sig ... end
val globalNameSpace: NameSpace.nameSpace

The NameSpace structure contains functions and types for printing the values produced by the compiler. globalNameSpace is the default name space. It contains the top-level bindings made by compiling source code with use or make or entering code at the top-level. Values in globalNameSpace can be extracted and displayed in the same way as any other values of the nameSpace type. Values can be deleted using Compiler.forgetValue etc.

structure Profiling : sig ... end

The Profiling structure contains functions to profile Poly/ML programs. As well as measuring the time spent in a function it can also measure the amount of memory allocated to identify functions that lead to excessive garbage-collection.

structure SaveState : sig ... end
val loadModule: string -> unit

The SaveState structure contains functions to save the current state to a file and restore the previous state. loadModule is a synonym for SaveState.loadModule.

structure Statistics : sig ... end

The Statistics structure contains functions to extract information about the Poly/ML run-time system. It can also be used to query statistics about other Poly/ML processes run by the same user.

val fullGC: unit -> unit

The fullGC function forces the run-time system to execute a full garbage collection. Normally this will not be required.

val pointerEq: 'a * 'a -> bool

The pointerEq function compares two values for identity. The normal ML rules for equality state that two values are equal if they have the same contents. The exception is mutable values, such as refs and arrays, which are considered equal only if they are the same ref or array. Equality is only possible for equality types, which excludes functions and values of type real.

PolyML.pointerEq can be applied to values of any type and returns true if the values are identifical. It is intended for use in user functions that wish to test for equality in a general sense and where a potentially expensive operation can be cut short if it is known that the arguments are actually the same. It should be used with care. Poly/ML only guarantees the ML equality property and the compiler and run-time system may merge or split immutable values. For example, shareCommonData, combines immutable values with the same contents.

val shareCommonData : 'a -> unit

The shareCommonData function is intended to reduce the storage requirements by merging immutable data, such as lists and strings, that have the same contents. Since the ML equality function tests for equality of contents rather than pointer equality, running shareCommonData does not affect the behaviour of an ML program unless it relies on non-standard functions such as PolyML.pointerEq. The shareCommonData function requires extra space on the heap and sorts the data before merging so can be expensive to run when the heap is large. The argument is the root of the data that should be shared. To share everything pass PolyML.rootFunction as the argument. It is frequently used before the heap is saved with PolyML.export or PolyML.SaveState.saveState.

val export: string * (unit -> unit) -> unit
val exportPortable: string * (unit -> unit) -> unit

The export and exportPortable functions are used to create object modules or object files. They both take arguments which are the name of the file to be written and a function to use as the root. All data reachable from the root are written to the output file. export writes an object file whose format depends on the particular operating system. On Linux and other similar systems this is an ELF object file, on Windows a PECOFF file and on Mac OS a Mach-O file. It can then be linked with the Poly/ML libraries to produce an executable file. exportPortable writes an operating-system-independent text file that can be read by the polyImport program. It is intended primarily to allow the Poly/ML system itself to be distributed by avoiding the necessity of having separate object files for each operating system. Note that the file contains machine code so while it is operating-system independent it is not independent of the architecture.

val onEntry: (unit -> unit) -> unit

The onEntry function adds a function to a list of functions that are executed when a Poly/ML program begins. It is primarily intended for libraries that need to be initialised.

val make: string -> unit

The make function is used to compile and build a set of modules from source. There is a separate description and tutorial.

val use: string -> unit

PolyML.use is the same function as the top-level use function. It takes a file name as its argument and compiles and executes the Standard ML code in the file. The file-name extension may be omitted if it is one of those listed in PolyML.suffixes. The inclusion of PolyML.use is really a relic from an early version of Standard ML which did not include a use function.

val getUseFileName: unit -> string option

This function can be called within a piece of ML code that is being compiled with use to return the file name and specifically the path that was used to refer to the file. It is useful if the ML code itself calls use because it can then adjust the path.

val suffixes = ref ["", ".ML", ".sml"]: string list ref

PolyML.suffixes holds a list of file-name extensions of ML source files. This is used by PolyML.use and PolyML.make. If they are given a file-name as an argument and the file does not exist they attempt to open a file with a name constructed from the original argument by adding each of the extensions in turn.

datatype ptProperties =
        PTbreakPoint of bool ref
| PTcompletions of string list
| PTdeclaredAt of location
| PTdefId of int
| PTfirstChild of unit -> parseTree
| PTnextSibling of unit -> parseTree
| PTopenedAt of location
| PTparent of unit -> parseTree
| PTpreviousSibling of unit -> parseTree
| PTprint of int -> pretty
| PTreferences of bool * location list
| PTrefId of int
| PTstructureAt of location
| PTtype of NameSpace.Values.typeExpression withtype parseTree = location * ptProperties list

The ptProperties datatype is associated with a parse-tree that has been exported from the compiler. The parse-tree is an abstract tree where each node has a location and a set of properties. The properties associated with a particular node vary according to the underlying parse-tree and circumstances.

PTfirstChild, PTnextSibling, PTpreviousSibling and PTparent are used to move between nodes. They are only present if the appropriate node exists. PPTfirstChild is present if the node has children and returns the first (leftmost) node. PTnextSibling and PTpreviousSibling return the next (right) and previous (left) nodes at the same level. PTparent returns the parent node.

PTtype returns a value that represents the type of the node. It is present on nodes that represent values and can have a type.

PTdeclaredAt, PTopenedAt, PTstructureAt and PTreferences may be present on nodes that represent identifiers. They provide location information. PTdeclaredAt provides the location where the identifier was declared. PTopenedAt and PTstructureAt are only present if the identifier came from a structure that had been opened and give the location where the structure had been opened and where the structure itself was declared.

PTprint produces a pretty-print structure of the node. It is present on most nodes. The argument is the depth of the data structure to produce before eliding the detail.

PTbreakPoint is only present if debugging has been enabled. It returns the debugger identifier for the node.

PTcompletions returns possible completions of an invalid identifier.

PTrefId and PTdefId properties are used with identifiers. PTdefId is a defining occurence of an identifier while PTrefId is a referencing occurrence.

datatype context =
       ContextLocation of location
   |   ContextProperty of string * string

The context type is primarily used by the IDE when providing error messages. For most purposes the context argument to PrettyBlock can be the empty list. ContextLocation provides the location associated with the item being printed, typically the location where it was declared. ContextProperty can be used by a user-supplied pretty printer to provide extra information which may be useful if the result of pretty printing is to be processed by a user function. It is not produced by Poly/ML pretty printers and the default printers ignore this item.

datatype pretty =
       PrettyBlock of int * bool * context list * pretty list
   |   PrettyBreak of int * int
   |   PrettyLineBreak
   |   PrettyString of string
   |   PrettyStringWithWidth of string * int

The pretty datatype is used in connection with formatting data for display. Rather than create text directly most display functions create values of this type. It is used with a pretty-printer based around the model described by D.C. Oppen in ACM ToPLAS Vol. 2 No. 4 Oct 1980.

PrettyString s contains a string to be printed.

PrettyStringWithWidth (s, n) is similar to PrettyString except that it contains an explicit width. It is used when the width of the displayed string is not the same as the number of characters. This may happen if the string contains UTF-8 encoded characters.

PrettyBlock(indent, consistent, context, items) defines a block of items which may be strings, breaks or blocks. The items will be retained on a single line if possible. The indent parameter is an indentation that will be added to the current indentation if the block has to be broken. Note that this does not apply to the first item in the block which will always be printed with the current indentation. The consistent parameter indicates whether the block is to be broken consistently (true) or not (false). If it is true then if the block will not all fit on a line and must be broken then it will be broken at all the breaks in the block whether this is necessary or not. If it is false it will only be broken where necessary. Neither of these parameters are used if the block will fit on a line.

PrettyBreak(blanks, breakOffset) indicates a break between items. If the line is not broken at this point then blanks is the number of space characters that will be inserted. If the line is broken at that point then instead the following item is indented by an extra breakOffset spaces.

PrettyLineBreak inserts an explicit line-break.

val prettyPrint : (string -> unit) * int -> pretty -> unit

prettyPrint provides a basic formatting of a pretty structure. prettyPrint (output, width) p formats the pretty structure p and calls the output function to display it. The width is the number of characters that can be displayed on a single line. Spaces and line-breaks are produced by calling output with strings containing spaces and the newline character "\n". Context information in PrettyBlock nodes is ignored.

val prettyMarkup :
       (context list -> unit) * (context list -> unit) ->
       (string -> unit) * int -> pretty -> unit
prettyMarkup provides the same formatting as prettyPrint except that it includes processing of context information. prettyMarkup(startC, endC) (output, width) p formats the pretty structure p using output and width with the addition that whenever a PrettyBlock is encountered with non-empty context list startC is called with the list before the block is output and endC is called with the same list when the block is complete.
val prettyPrintWithIDEMarkup : (string -> unit) * int -> pretty -> unit

prettyPrintWithIDEMarkup is the same as prettyPrint except that it adds IDE mark-up for location properties.

val addPrettyPrinter : (int -> 'a -> 'b -> pretty) -> unit

addPrettyPrinter is used to associate a printing function with a type so that it can be displayed in a more user-friendly form than the default. It appears to have a polymorphic type but is specially handled by the compiler. Pretty-printers can be installed for datatypes or types produced by opaque signature matching or returned from functors. It is not possible to install a pretty-printer for a type function (i.e. a type defined by type t = ...) unless the type function is simply giving an alternative name to a datatype.

addPrettyPrinter pp installs a pretty printer pp where pp has arguments depth printArgTypes value. The first argument, depth, is the print depth. This is a value that indicates how much of the data structure should be displayed. If this value is zero or negative the pretty printer should always print a simple string such as PrettyString "...". The intention is that the user can control how much output to produce when printing a large data structure. Larger values produce more of the structure. For values produced by the top-level this will generally be controlled by PolyML.print_depth. The third argument, value, is the actual value of the type that is to be printed. When installing a pretty printer there must be sufficient type constraint so that the compiler is able to determine the type unambiguiously.

The second argument, printArgTypes, is only used for polytypes i.e. datatypes with type parameters defined as 'a t or ('a, 'b', 'c ...) t. It is not used for monotypes. If the type takes a single argument then printArgTypes has type 'a * int -> pretty and is the function that will generate the pretty data structure for the argument type. The int argument is the adjusted print depth and should normally be one less than the value of depth. If the type takes multiple arguments then printArgTypes is a tuple with each field being a function of type 'a * int -> pretty that is used for the corresponding argument of the datatype.

val prettyRepresentation : 'a * int -> pretty

prettyRepresentation returns the pretty structure that can be used to print a value of the given type up to the specified depth. It is similar to PolyML.print in being infinitely overloaded. It can be useful when writing a pretty printer for a datatype that includes types that already have pretty printers installed or where they will be installed later since it uses any pretty printers for the types when it is actually called.

val print: 'a -> 'a

The PolyML.print function is a special infinitely overloaded function that prints its argument and returns the original result. It is treated specially by the compiler which uses type information to format the output.

val makestring: 'a -> string

The PolyML.makestring function is similar to PolyML.print in being infinitely overloaded. It returns a string representation of its argument.

val rootFunction : unit -> unit

PolyML.rootFunction is the initial function for the default Poly/ML system. It processes the command line arguments and then calls PolyML.shell to start the main top-level, read-eval-print loop. It is the root of everything in the Poly/ML system so it may be used as an argument to PolyML.shareCommonData to minimise the size of the heap before it is saved.

val shell : unit -> unit

The shell function runs the normal Poly/ML top-level read-eval-print loop (REPL). This function is provided for the situation where a user might wish to export an alternative top-level function but in some cases switch to the default. It returns only when it detects end-of-file on TextIO.stdIn. The top-level prompts the user for input using the value held in Compiler.prompt1, "> " by default, for the first line of a topdec. Subsequent lines are prompted for using the value in Compiler.prompt2, default "# ".

val architecture : unit -> string

The architecture function returns a string that identifies the computer architecture for which the current version of Poly/ML has been built and the code that the compiler generates. Currently, it returns one of the following values: "I386", "X86_64" or "Interpreted". The last is used when Poly/ML has been built for the portable byte-code version.

val rtsArgumentHelp : unit -> string

PolyML.rtsArgumentHelp returns a string that describes the command-line arguments that are used by the run-time system. The main purpose of this function is to enable Poly/ML programs that accept a command-line "help" argument to provide information to the user about arguments that will be interpreted by the run-time system in addition to information about their own arguments.

val rtsVersion : unit -> int

The rtsVersion function returns a number that identifies the version of the run-time system. For version 5.5.3 it returns 553.

val objSize: 'a -> int

PolyML.objSize returns the number of words of heap space used in its argument. It is primarily intended for debugging when the heap requirements of a program are larger than expected. It returns the total size of everything reachable from its argument. It takes account of sharing so if parts of the data structure are shared they will be counted only once.

val showSize : 'a -> int

PolyML.showSize returns the same result as objSize but as a side-effect it prints a low-level representation of the data to the standard output. It should be used with care as the output can be very large.

val objProfile : 'a -> int

PolyML.objProfile returns the same result as objSize but as a side-effect it prints a profile of the sizes of cells, the number of cells of each size. Separate profiles are produced for mutable cells i.e. refs and arrays, and immutable cells such as lists and tuples.

val stackTrace : unit -> unit

The stackTrace function prints a trace of the current stack; that is the name of the calling function and the function that called it back to the start of the thread.

val profiling : int -> unit

PolyML.profiling enables profiling for executed code. It takes an integer argument that sets the kind of profiling. Zero disables profiling; non-zero values enable different kinds of profiling.

val timing : bool -> unit

PolyML.timing enables or disables the timing of each expression typed at the top-level. For each expression figures are shown for various passes of the compiler and the execution time of the expression.

val sourceLocation : unit -> location

PolyML.sourceLocation is a function that is treated specially by the compiler. It returns the location in the source file where its call appears. Exactly what information is returned depends on what options have been provided to the compiler.

type location = {file: string, startLine: int, endLine: int, startPosition: int, endPosition: int}

The location type is associated with the compiler. It represents a position in some program source, usually a source file. The information that is included depends on the particular function that has returned the location and how the compiler was called. Generally, file will contain the source file name, startLine and endLine will contain the line numbers where the item started and ended and startPosition and endPosition the byte position in the file. The file name may be the empty string and any of the other arguments may be zero if the information is not available.

structure IntInf: sig val gcd : int * int -> int val lcm : int * int -> int end

The IntInf structure contains two functions that have been added to the arbitrary-precision arithmetic library. They compute the greatest common divisor and the lowest common multiple. If Poly/ML has been built to use the GMP library these functions will make use of that library.

val print_depth: int -> unit

PolyML.print_depth sets the depth for printing the results of each top-level expression. Passing zero or a negative value as the argument disables all printing of top-level results. When a large data structure is to be printed the print depth controls how much of the structure is displayed. PolyML.print_depth sets the value of PolyML.Compiler.printDepth.

val error_depth: int -> unit

PolyML.error_depth controls the amount of output to produce when printing information in an error message. It behaves in the same way as PolyML.print_depth. PolyML.error_depth sets the value of PolyML.Compiler.errorDepth.

val line_length: int -> unit

PolyML.line_length sets the length of a line which is used when produced pretty-printed output. PolyML.line_length sets the value of PolyML.Compiler.lineLength.