rkt. Alternatively, instead of using all-from-out and then naming bindings
to withhold, you could explicitly export
only certain pieces from racket.
The exports of txtadv.rkt completely
determine the bindings that are available in world.rkt—not only the functions, but also syntactic forms such
as require or lambda. For example,
txtadv.rkt could supply a lambda
binding to world.rkt that implements
a different kind of function than the
usual lambda, such as functions with
More commonly, a module language can replace the #%module-begin form that implicitly wraps the
body of a module. Specifically, txtadv.
rkt can provide an alternate #%mod-ule-body that forces world.rkt to
have a single define-verbs form, a
single define-everywhere form,
a sequence of define-thing declarations, and a sequence of define-place declarations; if world.rkt has
any other form, it can be rejected as a
syntax error. Such constraints can enforce restrictions to limit the power of
the txtadv.rkt language, but they can
also be used to provide domain-specific checking and error messages.
The game implemented with a
txtadv.rkt language is available online
(see the accompanying sidebar for url
information). The #%module-begin
replacement in the implementation
requires define-verbs followed by
define-everywhere, then allows
any number of other declarations. The
module must end with a place expres-
sion, which is used as the starting loca-
tion for the game.
The define-verb, define-place,
and define-thing forms bind
names in the same way as any other
Racket definition, and each reference
to a verb, place, or thing is a Racket-level reference to the defined name.
This approach makes it easy for verb-response expressions, which are implemented in Racket, to refer to other
things and places in the virtual world.
It also means, however, that misusing
a reference as a thing can lead to a runtime error. For example, the incorrect
reference to desert as a thing in
"You’re in the house."
Many languages provide type
checking or other static types to ensure the absence of certain runtime
errors. Racket macros can implement languages with static checks,
and macros can even implement language extensions that perform static
checks within a base language that
defers similar checks to runtime.
Specifically, you can adjust define-verb, define-place, and define-thing to check certain references,
such as requiring that the list of initial things in a place contain only
names that are defined as things.
Similarly, names used as verbs with
responses can be checked to ensure
they are declared as verbs, suitably
transitive or intransitive.
Implementing static checks typically requires macros that are more
expressive than pattern-matching
macros. In Racket, arbitrary compile-time code can perform the role of expander for a syntactic form, because
the most general form of a macro definition is
(define-syntax id transformer-expr )
triggers a failure only when the player
enters room, and the game engine fails
when trying to print the things within
figure 8. the revised define-place macro with type declaration.
(define-syntax-rule (define-place id ....)
(define gen-id (place ....)) ; as before
(define-syntax id (typed #'gen-id "place"))
(record-element! 'id id )))
figure 9. the revised define-place macro with type checking.
(define-syntax-rule (define-place id
([vrb expr ] ...))
(list (check-type thng "thing") ...)
(list (cons (check-type vrb "intransitive verb")
(lambda () expr ))
(define-syntax id (typed #'gen-id "place"))
(record-element! ’id id )))
where transformer-expr is a compile-time expression that produces a
function. The function must accept
one argument, which is a representation of a use of the id syntactic form,
and the function must produce a representation of the use’s expansion. In
the same way that define-syntax-rule is shorthand for define-syntax
plus syntax-rules and a single pattern, syntax-rules is shorthand for
a function of one argument that pulls
apart expressions of a certain shape
(matching a pattern) and constructs a
new expression for the result (based
on a template).
The compile-time language that is
used for transformer-expr can
be different from the surrounding
runtime language, but #lang racket
seeds the language of compile-time
expressions with essentially the same
language as for runtime expressions.
New bindings can be introduced to
the compile-time phase with (
require (for-syntax ....))instead
of just require, and local bindings
can be added to the compile-time
phase through definitions wrapped