The Nearly Complete Scheme48 PreScheme 1.3 Reference Manual

Table of Contents

Next: , Up: (dir)   [Contents]

The Nearly Complete Scheme48 PreScheme Reference Manual

This manual is for Scheme48 version 1.3.

Copyright © 2004, 2005, 2006 Taylor Campbell. All rights reserved.

This manual includes material derived from works bearing the following notice:

Copyright © 1993–2005 Richard Kelsey, Jonathan Rees, and Mike Sperber. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Previous: , Up: Top   [Contents]

1 Pre-Scheme: A low-level dialect of Scheme

Pre-Scheme [Kelsey 97] is a low-level dialect of Scheme, designed for systems programming with higher-level abstractions. For example, the Scheme48 virtual machine is written in Pre-Scheme. Pre-Scheme is a particularly interesting alternative to C for many systems programming tasks, because not only does it operate at about the same level as C, but it also may be run in a regular high-level Scheme development with no changes to the source, without resorting to low-level stack munging with tools such as gdb. Pre-Scheme also supports two extremely important high-level abstractions of Scheme: macros and higher-order, anonymous functions. Richard Kelsey’s Pre-Scheme compiler, based on his PhD research on transformational compilation [Kelsey 89], compiles Pre-Scheme to efficient C, applying numerous intermediate source transformations in the process.

This chapter describes details of the differences between Scheme and Pre-Scheme, listings of the default environment and other packages available to Pre-Scheme, the operation of Richard Kelsey’s Pre-Scheme compiler, and how to run Pre-Scheme code as if it were Scheme in a regular Scheme environment.


Next: , Up: Pre-Scheme   [Contents]

1.1 Differences between Pre-Scheme & Scheme

Pre-Scheme is often considered either a dialect of Scheme or a subset of Scheme. However, there are several very important fundamental differences between the semantics of Pre-Scheme & Scheme to detail.

There is no garbage collector in Pre-Scheme.

All memory management is manual, as in C, although there are two levels to memory management, for higher- and lower-level purposes: pointers & addresses. Pointers represent higher-level data that are statically checked for type coherency, such as vectors of a certain element type, or strings. Addresses represent direct, low-level memory indices.

Pre-Scheme has no closures.

Lambda expressions that would require full closures at run-time — e.g., those whose values are stored in the heap — are not permitted in Pre-Scheme. However, the Pre-Scheme compiler can hoist many lambda expressions to the top level, removing the need of closures for them. (Closures would be much less useful in the absence of garbage collection, in any case.) If the Pre-Scheme compiler is unable to move a lambda to a place where it requires no closure, it signals an error to the user.

Tail call optimization is not universal.

The Pre-Scheme compiler optimizes tail calls where it is possible — typically, just in local loops and top-level procedures that are not exported from the package, but there are other heuristics —, but it is not universal. Programmers may force tail call optimization with Pre-Scheme’s goto special form (see Tail call optimization in Pre-Scheme), but, in situations where the compiler would not have optimized the tail call, this can make the generated code have to jump through many hoops to be a tail call — often necessitating code bloat, because the code of the tail-called procedure is integrated into the caller’s driver loop —; and, where the compiler would have otherwise optimized the tail call, goto has no effect anyway.

Types are strictly verified with Hindley-Milner type inference.

The types of Pre-Scheme programs are statically verified based on Hindley-Milner type inference, with some modifications specific to Pre-Scheme. Type information is not retained at run-time; any tagging must be performed explicitly.

Pre-Scheme does not support first-class continuations.

There is no call-with-current-continuation or other continuation manipulation interface. It has been suggested that downward-only continuations, based on C’s setjmp & longjmp, might be implemented in the future, but this is not yet the case.1

The full numeric tower of R5RS is not supported by Pre-Scheme.

Pre-Scheme’s only numeric types are fixnums and flonums, with precision determined by the architecture on which the Pre-Scheme code runs. Fixnums are translated to C as the long type; flonums are translated as the float type.

Top-level Pre-Scheme code is evaluated at compile-time.

Closures actually are available, as long as they may be eliminated before run-time. Code evaluated at compile-time also does not require satisfaction of strict static typing. Moreover, certain procedures, such as vector-length, are available only at compile-time.


Next: , Previous: , Up: Pre-Scheme   [Contents]

1.2 Type specifiers

Although Pre-Scheme’s static type system is based mostly on Hindley-Milner type inference, with as little explicit type information as possible, there are still places where it is necessary to specify types explicitly; for example, see Pre-Scheme access to C functions and macros. There are several different kinds of types with different syntax:

type-name

Symbols denote either record type or base types. Record types are defined with the define-record-type special form described later; the following base types are defined:

integer

Fixed-size integers (fixnums). This type is translated into C as long. The actual size depends on the size of C’s long, which on most architectures is 32 bits.

float

Floating-point data. This type translates to C as double.

null

Type which has no value. The null type translates to the C void type.

unit

Type which has one value. Actually, this, too, translates to C’s void, so that it has one value is not strictly true.

boolean

Booleans translate to the C char type. #t is emitted as TRUE, and #f, as FALSE; these are usually the same as 1 & 0, respectively.

input-port
output-port

I/O ports. On Unix, since Pre-Scheme uses stdio, these are translated to FILE *s, stdio file streams.

char

Characters. The size of characters is dependent on the underlying C compiler’s implementation of the char type.

address

Simple addresses for use in Pre-Scheme’s low-level memory manipulation primitives; see that section for more details.

(=> (argument-type …) return-type …)

The types of procedures, known as ‘arrow’ types.

(^ type)

The type of pointers that point to type. Note that these are distinct from the address type. Pointer types are statically verified to be coherent data, with no defined operations except for accessing offsets in memory from the pointer — i.e. operations such as vector-ref —; addresses simply index bytes, on which only direct dereferencing, but also arbitrary address arithmetic, is available. Pointers and addresses are not interchangeable, and and there is no way to convert between them, as that would break the type safety of Pre-Scheme pointers.

(tuple type …)

Multiple value types, internally used for argument & return types.


Next: , Previous: , Up: Pre-Scheme   [Contents]

1.3 Standard environment

Pre-Scheme programs usually open the prescheme structure. There are several other structures built-in to Pre-Scheme as well, described in the next section. This section describes the prescheme structure.


Next: , Up: Standard Pre-Scheme environment   [Contents]

1.3.1 Scheme bindings

Bindings for all the names specified here from R5RS Scheme are available in Pre-Scheme. The remainder of the sections after this one detail Pre-Scheme specifics that are not a part of Scheme.

syntax: define name value
syntax: define (name . argument-list) value
syntax: if condition consequent [alternate]
syntax: let ((name expression) …) body
syntax: let* ((name expression) …) body
syntax: and conjunct …
syntax: or disjunct …
syntax: cond cond-clause …
syntax: do ((name init-exp [step-exp]) …) (test-exp [return-exp]) body

These special forms & macros are all unchanged from their R5RS specifications.

syntax: define-syntax name transformer-expression [aux-names]
syntax: let-syntax ((name transformer-expression) …) body
syntax: letrec-syntax ((name transformer-expression) …) body

Pre-Scheme’s macro facility is exactly the same as Scheme48’s. Transformer-expression may be either a syntax-rules or an explicit renaming transformer, just as in Scheme48; in the latter case, it is evaluated either in a standard Scheme environment or however the for-syntax clause specified of the package in whose code the transformer appeared. For details on the extra aux-names operand to define-syntax, see ‘Explicit renaming macros’.

procedure: not boolean --> boolean
procedure: eq? valuea
procedure: char=? chara
procedure: char<? chara
procedure: values value … --> values
procedure: call-with-values producer consumer --> values
procedure: current-input-port --> input-port
procedure: current-output-port --> output-port

These procedures are all unchanged from their R5RS specifications.

procedure: + addend … --> integer
procedure: - integer --> integer
procedure: - minuend subtrahend --> integer
procedure: * multiplicand … --> integer
procedure: = integera
procedure: < integera
procedure: > integera
procedure: <= integera
procedure: >= integera
procedure: min integer1
procedure: max integer1
procedure: abs integer --> integer
procedure: quotient divisor dividend --> integer
procedure: remainder divisor dividend --> integer
procedure: expt base exponent --> integer

These numerical operations are all unchanged from their R5RS counterparts, except that they are applicable only to fixnums, not to flonums, and they always return fixnums.


Next: , Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.2 Tail call optimization

syntax: goto procedure argument …

The Pre-Scheme compiler can be forced to optimize tail calls, even those it would not have otherwise optimized, by use of the goto special form, rather than simple procedure calls. In every respect other than tail call optimization, this is equivalent to calling procedure with the given arguments. Note, however, that uses of goto may cause code to blow up if the Pre-Scheme compiler had reason not to optimize the tail call were it not for the goto: it may need to merge the tail-called procedure into the caller’s code.


Next: , Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.3 Bitwise manipulation

Pre-Scheme provides basic bitwise manipulation operators.

procedure: bitwise-and integera
procedure: bitwise-ior integera
procedure: bitwise-xor integera
procedure: bitwise-not integer --> integer

Bitwise boolean logical operations.

procedure: shift-left integer count --> integer
procedure: arithmetic-shift-right integer count --> integer
procedure: logical-shift-right integer count --> integer

Three ways to shift bit strings: shift-left shifts integer left by count, arithmetic-shift-right shifts integer right by count arithmetically, and logical-shift-right shifts integer right by count logically.


Next: , Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.4 Compound data manipulation

Pre-Scheme has somewhat lower-level vector & string facilities than Scheme, with more orientation towards static typing. It also provides a statically typed record facility, which translates to C structs, though not described here, as it is not in the prescheme structure; see Pre-Scheme record types.

procedure: make-vector length init --> vector
procedure: vector-length vector --> integer
procedure: vector-ref vector index --> value
procedure: vector-set! vector index value --> unit

Vectors in Pre-Scheme are almost the same as vectors in regular Scheme, but with a few differences. Make-vector initializes what it returns with null pointers (see below); it uses the required (unlike Scheme) init argument only to determine the type of the vector: vectors are statically typed; they can contain only values that have the same static type as init. Vector-length is available only at the top level, where calls to it can be evaluated at compile-time; vectors do not at run-time store their lengths. Vectors must also be explicitly deallocated.

Warning: As in C, there is no vector bounds checking at run-time.

procedure: make-string length --> string
procedure: string-length string --> integer
procedure: string-ref string index --> char
procedure: string-set! string index char --> unit

Strings in Pre-Scheme are the nearly same as strings in R5RS Scheme. The only three differences here are that make-string accepts exactly one argument, strings must be explicitly deallocated, and strings are nul-terminated: string-length operates by scanning for the first ASCII nul character in a string.

Warning: As in C, there is no string bounds checking at run-time.

procedure: deallocate pointer --> unit

Deallocates the memory pointed to by pointer. This is necessary at the end of a string, vector, or record’s life, as Pre-Scheme data are not automatically garbage-collected.

procedure: null-pointer --> null-pointer
procedure: null-pointer? pointer --> boolean

Null-pointer returns the distinguished null pointer object. It corresponds with 0 in a pointer context or NULL in C. Null-pointer? returns true if pointer is a null pointer, or false if not.


Next: , Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.5 Error handling

Pre-Scheme’s method of error handling is similar to the most common one in C: error codes. There is an enumeration errors of some error codes commonly and portably encountered in Pre-Scheme.

enumeration: errors
(define-enumeration errors
  (no-errors
   parse-error
   file-not-found
   out-of-memory
   invalid-port))

Each enumerand has the following meaning:

no-errors

Absence of error: success.

parse-error

Any kind of parsing error. The Scheme48 VM uses this when someone attempts to resume a malformed suspended heap image.

file-not-found

Used when an operation that operates on a file given a string filename found that the file for that filename was absent.

out-of-memory

When there is no more memory to allocate.

invalid-port

Unused.

procedure: error-string error-status --> string

Returns a string describing the meaning of the errors enumerand error-status.

procedure: error message irritant …

Signals a fatal error with the given message & related irritants and halts the program. On Unix, the program’s exit code is -1.


Next: , Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.6 Input & output

Pre-Scheme’s I/O facilities are somewhat different from Scheme’s, given the low level and the static type strictness. There is no exception mechanism in Pre-Scheme; everything is maintained by returning a status token, as in C. Pre-Scheme’s built-in I/O facilities are buffered. 2 (see Low-level Pre-Scheme memory manipulation, for two other I/O primitives, read-block & write-block, for reading & writing blocks of direct memory.)

procedure: open-input-file filename --> [port status]
procedure: open-output-file filename --> [port status]
procedure: close-input-port input-port --> status
procedure: close-output-port output-port --> status

Open-input-file & open-output-file open ports for the given filenames. They each return two values: the newly open port and an errors enumerand status. Users of these procedures should always check the error status before proceeding to operate with the port. Close-input-port & close-output-port close their port arguments and return the errors enumerand status of the closing.

procedure: read-char input-port --> [char eof? status]
procedure: peek-char input-port --> [char eof? status]
procedure: read-integer input-port --> [integer eof? status]

Read-char reads & consumes a single character from its input-port argument. Peek-char reads, but does not consume, a single character from input-port. Read-integer parses an integer literal, including sign. All of these also return two other values: whether or not the file is at the end and any errors enumerand status. If any error occurred, the first two values returned should be ignored. If status is (enum errors no-errors), users of these three procedures should then check eof?; it is true if input-port was at the end of the file with nothing more left to read and false otherwise. Finally, if both status is (enum errors no-errors) and eof? is false, the first value returned may be safely used.

procedure: write-char char output-port --> status
procedure: newline output-port --> status
procedure: write-string string output-port --> status
procedure: write-integer integer output-port --> status

These all write particular elements to their output-port arguments. Write-char writes individual characters. Newline writes newlines (line-feed, or ASCII codepoint 10, on Unix). Write-string writes the contents of string. Write-integer writes an ASCII representation of integer to port, suitable to be read by read-integer. These all return an errors enumerand status. If it is no-errors, the write succeeded.

procedure: force-output output-port --> status

Forces all buffered output in output-port. Status tells whether or not the operation was successful.


Previous: , Up: Standard Pre-Scheme environment   [Contents]

1.3.7 Access to C functions and macros

syntax: external c-name ps-type --> procedure

Special form for accessing C functions & macros. Calls in Pre-Scheme to the resulting procedure are compiled to calls in C to the function or macro named by c-name, which should be a string. PS-type is the Pre-Scheme type that the procedure should have, which is necessary for type inference.


Next: , Previous: , Up: Pre-Scheme   [Contents]

1.4 More Pre-Scheme packages

Along with the prescheme structure, there are several other structures built-in to Pre-Scheme.


Next: , Up: More Pre-Scheme packages   [Contents]

1.4.1 Floating point operation

Since Pre-Scheme’s strict static type system would not permit overloading of the arithmetic operators for integers & floats, it provides a different set of operators for floats. These names are all exported by the ps-flonums structure.

procedure: fl+ augend addend … --> float
procedure: fl- float --> float
procedure: fl- minuend subtrahend --> float
procedure: fl* multiplier multiplicand … --> float
procedure: fl/ divisor dividend --> float
procedure: fl= floata
procedure: fl< floata
procedure: fl> floata
procedure: fl<= floata
procedure: fl>= floata

All of these operations flop correspond as floating point variations of their op integer equivalents.


Next: , Previous: , Up: More Pre-Scheme packages   [Contents]

1.4.2 Record types

The ps-record-types structure defines the following special form for introducing record types. Pre-Scheme record types are translated to C as structs.

syntax: define-record-type
(define-record-type type type-descriptor
  (constructor argument-field-tag …)
  (field-tag<1> field-type-spec<1>
    field-accessor<1> [field-modifier<1>])
  (field-tag<2> field-type-spec<2>
    field-accessor<2> [field-modifier<2>])
  …
  (field-tag<n> field-type-spec<n>
    field-accessor<n> [field-modifier<n>])

Defines a record type. Type is mangled to the C struct type name (type-descriptor-name is unused unless running Pre-Scheme as Scheme). Constructor is defined to construct a record of the new type and initialize the fields argument-type-field … with its arguments, respectively. If it cannot allocate a sufficient quantity of memory, constructor returns a null pointer. The initial values of fields that are not passed to the constructor are undefined. For each field field<i> specified,

Records must be deallocated explicitly when their lifetime has expired with deallocate.


Next: , Previous: , Up: More Pre-Scheme packages   [Contents]

1.4.3 Multiple return values

Pre-Scheme support multiple return values, like in Scheme. The only difference is that one cannot operate on multiple return values as lists, since Pre-Scheme does not have lists. Multiple return values are implemented in C as returning in C the first value and passing pointers to the remaining values, which the function returning multiple values assigns. The prescheme structure exports the two multiple return value primitives, call-with-values and values, but the ps-receive structure exports this macro for more conveniently binding multiple return values.

syntax: receive formals producer body

Binds the lambda parameter list formals to the multiple values that producer returns, and evaluates body with the new variables bound.

(receive formals
         producer
  body)
    ≡
(call-with-values
    (lambda () producer)
  (lambda formals
    body))

Previous: , Up: More Pre-Scheme packages   [Contents]

1.4.4 Low-level memory manipulation

Pre-Scheme is a low-level language. It provides very low-level, direct memory manipulation. ‘Addresses’ index a flat store of sequences of bytes. While Pre-Scheme ‘pointers’ are statically checked for data coherency, allow no arbitrary arithmetic, and in general are high-level abstract data to some extent, addresses are much lower-level, have no statically checked coherency — the values an address represents are selected by what operation used to read or write from it —, permit arbitrary address arithmetic, and are a much more concrete interface into direct memory. The ps-memory structure exports these direct memory manipulation primitives.

procedure: allocate-memory size --> address
procedure: deallocate-memory address --> unit

Allocate-memory reserves a sequence of size bytes in the store and returns an address to the first byte in the sequence. Deallocate-memory releases the memory at address, which should have been the initial address of a contiguous byte sequence, as allocate-memory would return, not an offset address from such an initial address.

procedure: unsigned-byte-ref address --> unsigned-byte
procedure: unsigned-byte-set! address unsigned-byte --> unit
procedure: word-ref address --> word
procedure: word-set! address word --> unit
procedure: flonum-ref address --> float
procedure: flonum-set! address float --> unit

Procedures for reading from & storing to memory. Unsigned-byte-ref & unsigned-byte-set! access & store the first unsigned byte at address. Word-ref & word-set! access & store the first word — Pre-Scheme integer — beginning at address. Flonum-ref & flonum-set! access & store 64-bit floats beginning at address..

Bug: Flonum-ref & flonum-set! are unimplemented in the Pre-Scheme-as-Scheme layer (see Running Pre-Scheme as Scheme).

procedure: address? value --> boolean

Disjoint type predicate for addresses.

Note: Address? is available only at the top level, where code is evaluated at compile-time. Do not use this in any place where it may be called at run-time.

constant: null-address --> address

The null address. This is somewhat similar to the null pointer, except that it is an address.

Note: One acquires the null pointer by calling the procedure null-pointer, whereas the constant value of the binding named null-address is the null address.

procedure: null-address? address --> boolean

Null-address? returns true if address is the null address and false if not.

procedure: address+ address increment --> address
procedure: address- address decrement --> address
procedure: address-difference addressa

Address arithmetic operators. Address+ adds increment to address; address- subtracts decrement from address; and address-difference returns the integer difference between address<a> and address<b>. For any address<p> & address<q>, (address+ address<p> (address-difference address<p> address<q>)) is equal to address<q>.

procedure: address= addressa
procedure: address< addressa
procedure: address> addressa
procedure: address<= addressa
procedure: address>= addressa

Address comparators.

procedure: integer->address integer --> address
procedure: address->integer address --> integer

Integers and addresses, although not the same type, may be converted to and from each other; integer->address & address->integer perform this conversion. Note that Pre-Scheme pointers may not be converted to addresses or integers, and the converse is also true.

procedure: copy-memory! source-address target-address count --> unit

Copies count bytes starting at source-address to target-address. This is similar to C’s memcpy.

procedure: memory-equal? addressa

Compares the two sequences of count bytes starting at addresses address<a> & address<b>. It returns true if every byte is equal and false if not.

procedure: char-pointer->string address size --> string
procedure: char-pointer->nul-terminated-string address --> string

Char-pointer->string returns a string with size bytes from the contiguous sequence of bytes starting at address. Char-pointer->nul-terminated-string does similarly, but it returns a string whose contents include every byte starting at address until, but not including, the first 0 byte, i.e. ASCII nul character, following address.

procedure: read-block port address count --> [count-read eof? status]
procedure: write-block port address count --> status

Read-block attempts to read count bytes from port into memory starting at address. Write-block attempts to write count bytes to port from the contiguous sequence in memory starting at address. Read-block returns three values: the number of bytes read, whether or not the read went to the end of the file, and the error status (see Pre-Scheme error handling). Write-block returns the error status.


Next: , Previous: , Up: Pre-Scheme   [Contents]

1.5 Invoking the Pre-Scheme compiler

Richard Kelsey’s Pre-Scheme compiler is a whole-program compiler based on techniques from his research in transformational compilation [Kelsey 89]. It compiles the restricted dialect of Scheme to efficient C, and provides facilities for programmer direction in several optimizations.

1.5.1 Loading the compiler

There is a script, a Scheme48 ‘command program’, that comes with Scheme48 to load the Pre-Scheme compiler, which is in the file ps-compiler/load-ps-compiler.scm. It must be loaded from the ps-compiler/ directory, from Scheme48’s main distribution, into the exec package, after having loaded ../scheme/prescheme/interface.scm & ../scheme/prescheme/package-defs.scm into the config package. The Pre-Scheme compiler takes some time to load, so it may be easier to load it once and dump a heap image of the suspended command processor after having loaded everything; see ‘Image-building commands’.

To load the Pre-Scheme compiler and dump an image to the file ps-compiler.image that contains prescheme-compiler in the user package, send this sequence of commands to the command processor while in the ps-compiler/ directory of Scheme48’s distribution:

,config ,load ../scheme/prescheme/interface.scm
,config ,load ../scheme/prescheme/package-defs.scm
,exec ,load load-ps-compiler.scm
,in prescheme-compiler prescheme-compiler
,user (define prescheme-compiler ##)
,dump ps-compiler.image "(Pre-Scheme)"

1.5.2 Calling the compiler

After having loaded the Pre-Scheme compiler, the prescheme-compiler structure is the front end to the compiler that exports the prescheme-compiler procedure.

procedure: prescheme-compiler structure-spec config-filenames init-name c-filename command …

Invokes the Pre-Scheme compiler. Config-filenames contain module descriptions (see ‘Module system’) for the components of the program. Structure-spec may be a symbol or a list of symbols, naming the important structure or structures. All structures that it relies/they rely on are traced in the packages’ open clauses. Modules that are not traced in the dependency graph with root vertices of the given structure[s] are omitted from the output. C-filename is a string naming the file to which the C code generated by the Pre-Scheme compiler should be emitted. Init-name is the name for an initialization routine, generated automatically by the Pre-Scheme compiler to initialize some top-level variables. The command arguments are used to control certain aspects of the compilation. The following commands are defined:

(copy (structure copyable-procedure) …)

Specifies that each the body of each copyable-procedure from the respective structure (from one of config-filenames) may be integrated & duplicated.

(no-copy (structure uncopyable-procedure) …)

Specifies that the given procedures may not be integrated.

(shadow ((proc-structure procedure) (var-structure variable-to-shadow) …) …)

Specifies that, in procedure from proc-structure, the global variables variable-to-shadow from their respective var-structures should be shadowed with local variables, which are more likely to be kept in registers for faster operation on them.

(integrate (client-procedure integrable-procedure) …)

Forces integrable-procedure to be integrated in client-procedure.

Note: The integrate command operates on the global program, not on one particular module; each client-procedure and integrable-procedure is chosen from all variables defined in the entirety of the program, across all modules. It is advised that there be only one of each.

(header header-line …)

Each header-line is added to the top of the generated C file, after a cpp inclusion of <stdio.h> and "prescheme.h".

The command arguments to prescheme-compiler are optional; they are used only to optimize the compiled program at the programmer’s request.


Next: , Previous: , Up: Pre-Scheme   [Contents]

1.6 Example Pre-Scheme compiler usage

The ps-compiler/compile-vm.scm, ps-compiler/compile-gc.scm, and ps-compiler/compile-vm-no-gc.scm files give examples of running the Pre-Scheme compiler. They are Scheme48 ‘command programs’, to be loaded into the exec package after having already loaded the Pre-Scheme compiler. compile-vm.scm & compile-vm-no-gc.scm generate a new scheme48vm.c in the scheme/vm/ directory — compile-vm.scm includes the garbage collector, while compile-vm-no-gc.scm does not 3 —, and compile-gc.scm generates a new scheme48heap.c, scheme48read-image.c, & scheme48write-image.c in the scheme/vm/ directory.

Here is a somewhat simpler example. It assumes a pre-built image with the Pre-Scheme compiler loaded is in the ps-compiler.image file in the current directory (see Invoking the Pre-Scheme compiler, where there is a description of how to dump an image with the Pre-Scheme compiler loaded).

% ls
hello.scm               packages.scm            ps-compiler.image
% cat hello.scm
(define (main argc argv)
  (if (= argc 2)
      (let ((out (current-output-port)))
        (write-string "Hello, world, " out)
        (write-string (vector-ref argv 1) out)
        (write-char #\! out)
        (newline out)
        0)
      (let ((out (current-error-port)))
        (write-string "Usage: " out)
        (write-string (vector-ref argv 0) out)
        (write-string " <user>" out)
        (newline out)
        (write-string "  Greets the world & <user>." out)
        (newline out)
        -1)))
% cat packages.scm
(define-structure hello (export main)
  (open prescheme)
  (files hello))
% scheme48 -i ps-compiler.image
heap size 3000000 is too small, using 4770088
Welcome to Scheme 48 1.3 (Pre-Scheme)
Copyright (c) 1993-2005 by Richard Kelsey and Jonathan Rees.
Please report bugs to scheme-48-bugs@s48.org.
Get more information at http://www.s48.org/.
Type ,? (comma question-mark) for help.
> (prescheme-compiler 'hello '("packages.scm") 'hello-init "hello.c")
packages.scm
 hello.scmChecking types
 main : ((integer **char) -> integer)
In-lining single-use procedures
Call Graph:
<procedure name>
  <called non-tail-recursively>
  <called tail-recursively>
main  (exported)
Merging forms
Translating
 main
#{Unspecific}
> ,exit
% cat hello.c
#include <stdio.h>
#include "prescheme.h"

long main(long, char**);


long main(long argc_0X, char **argv_1X)
{
  FILE * out_3X;
  FILE * out_2X;
 {  if ((1 == argc_0X)) {
    out_2X = stdout;
    ps_write_string("Hello, world, ", out_2X);
    ps_write_string((*(argv_1X + 1)), out_2X);
    { long ignoreXX;
    PS_WRITE_CHAR(33, out_2X, ignoreXX) }
    { long ignoreXX;
    PS_WRITE_CHAR(10, out_2X, ignoreXX) }
    return 0;}
  else {
    out_3X = stderr;
    ps_write_string("Usage: ", out_3X);
    ps_write_string((*(argv_1X + 0)), out_3X);
    ps_write_string(" <user>", out_3X);
    { long ignoreXX;
    PS_WRITE_CHAR(10, out_3X, ignoreXX) }
    ps_write_string("  Greets the world & <user>.", out_3X);
    { long ignoreXX;
    PS_WRITE_CHAR(10, out_3X, ignoreXX) }
    return -1;}}
}
% 

Previous: , Up: Pre-Scheme   [Contents]

1.7 Running Pre-Scheme as Scheme

To facilitate the operation of Pre-Scheme systems within a high-level Scheme development environment, Scheme48 simply defines the prescheme, ps-memory, ps-record-types, ps-flonums, and ps-receive structures in terms of Scheme; Pre-Scheme structures can be loaded as regular Scheme structures because of this. Those structures and the interfaces they implement are defined in the files scheme/prescheme/interface.scm and scheme/prescheme/package-defs.scm from the main Scheme48 distribution; simply load these files into the ‘config package’ before loading any Pre-Scheme configuration files.

The Pre-Scheme emulation layer in Scheme has some shortcomings:


Footnotes

(1)

It may be possible to use Pre-Scheme’s C FFI to manually use setjmp & longjmp, but the author of this manual cannot attest to this working.

(2)

Scheme48’s VM does not use Pre-Scheme’s built-in I/O facilities to implement ‘channels’ — it builds its own lower-level facilities that are still OS-independent, but, because they’re written individually for different OSs, they integrate better as low-level I/O channels with the OS. On Unix, the Scheme48 VM uses file descriptors; Pre-Scheme’s built-in I/O uses stdio. Scheme48’s VM uses Pre-Scheme’s built-in I/O only to read heap images.

(3)

The actual distribution of Scheme48 separates the garbage collector and the main virtual machine.