improve guide chapter on language creation (based on Matthias's comments)
This commit is contained in:
parent
bb26115591
commit
322a045a51
|
@ -1,13 +1,19 @@
|
||||||
#lang scribble/doc
|
#lang scribble/doc
|
||||||
@(require scribble/manual
|
@(require scribble/manual
|
||||||
scribble/eval
|
scribble/eval
|
||||||
|
scribble/racket
|
||||||
"guide-utils.ss"
|
"guide-utils.ss"
|
||||||
"modfile.rkt"
|
"modfile.rkt"
|
||||||
|
(for-syntax racket/base)
|
||||||
(for-label setup/dirs
|
(for-label setup/dirs
|
||||||
syntax/strip-context
|
syntax/strip-context
|
||||||
syntax-color/default-lexer))
|
syntax-color/default-lexer))
|
||||||
|
|
||||||
@title[#:tag "hash-languages"]{Defining new @hash-lang[] Languages}
|
@(define-syntax ! (make-element-id-transformer (lambda (v) #'@tt{|})))
|
||||||
|
@(define-syntax !- (make-element-id-transformer (lambda (v) #'@tt{|-})))
|
||||||
|
|
||||||
|
|
||||||
|
@title[#:tag "hash-languages" #:style 'toc]{Defining new @hash-lang[] Languages}
|
||||||
|
|
||||||
When loading a module as a source program that starts
|
When loading a module as a source program that starts
|
||||||
|
|
||||||
|
@ -24,8 +30,10 @@ forms. Thus, a @racket[_language] specified after @hash-lang[]
|
||||||
controls both the @tech{reader}-level and @tech{expander}-level
|
controls both the @tech{reader}-level and @tech{expander}-level
|
||||||
parsing of a module.
|
parsing of a module.
|
||||||
|
|
||||||
|
@local-table-of-contents[]
|
||||||
|
|
||||||
@; ----------------------------------------
|
@; ----------------------------------------
|
||||||
@section{Designating a @hash-lang[] Language}
|
@section[#:tag "hash-lang syntax"]{Designating a @hash-lang[] Language}
|
||||||
|
|
||||||
The syntax of a @racket[_language] intentionally overlaps with the
|
The syntax of a @racket[_language] intentionally overlaps with the
|
||||||
syntax of a module path as used in @racket[require] or as a
|
syntax of a module path as used in @racket[require] or as a
|
||||||
|
@ -49,13 +57,13 @@ fixed so that various different tools can ``boot'' into the extended
|
||||||
world.
|
world.
|
||||||
|
|
||||||
Fortunately, the @hash-lang[] protocol provides a natural way to refer
|
Fortunately, the @hash-lang[] protocol provides a natural way to refer
|
||||||
to languages in ways other than the rigid
|
to languages in ways other than the rigid @racket[_language] syntax:
|
||||||
@racket[_language] syntax: by defining a @racket[_language]
|
by defining a @racket[_language] that implements its own nested
|
||||||
that implements its own nested protocol. We have already seen one example in
|
protocol. We have already seen one example (in @secref["s-exp"]): the
|
||||||
@secref["s-exp"]: the @racketmodname[s-exp] @racket[_language] allows
|
@racketmodname[s-exp] @racket[_language] allows a programmer to
|
||||||
a programmer to specify a @tech{module language} using the general
|
specify a @tech{module language} using the general @tech{module path}
|
||||||
@tech{module path} syntax. Meanwhile, @racketmodname[s-exp] takes care
|
syntax. Meanwhile, @racketmodname[s-exp] takes care of the
|
||||||
of the @tech{reader}-level responsibilities of a @hash-lang[] language.
|
@tech{reader}-level responsibilities of a @hash-lang[] language.
|
||||||
|
|
||||||
Unlike @racketmodname[racket], @racketmodname[s-exp] cannot be used as a
|
Unlike @racketmodname[racket], @racketmodname[s-exp] cannot be used as a
|
||||||
module path with @scheme[require]. Although the syntax of
|
module path with @scheme[require]. Although the syntax of
|
||||||
|
@ -87,8 +95,8 @@ language} at the @tech{expander} layer of parsing,
|
||||||
@racketmodname[reader] lets a programmer specify a language at the
|
@racketmodname[reader] lets a programmer specify a language at the
|
||||||
@tech{reader} level.
|
@tech{reader} level.
|
||||||
|
|
||||||
The module specified after @racket[@#,hash-lang[]
|
A @racket[@#,hash-lang[] @#,racketmodname[reader]] must be followed by
|
||||||
@#,racketmodname[reader]] must provide two functions:
|
a module path, and the specified module must provide two functions:
|
||||||
@racketidfont{read} and @racketidfont{read-syntax}. The protocol is
|
@racketidfont{read} and @racketidfont{read-syntax}. The protocol is
|
||||||
the same as for a @racketmetafont{#reader} implementation, but for
|
the same as for a @racketmetafont{#reader} implementation, but for
|
||||||
@hash-lang[], the @racketidfont{read} and @racketidfont{read-syntax}
|
@hash-lang[], the @racketidfont{read} and @racketidfont{read-syntax}
|
||||||
|
@ -137,17 +145,19 @@ the creation of new languages. In its most basic form, a language
|
||||||
implemented with @racketmodname[syntax/module-reader] simply specifies
|
implemented with @racketmodname[syntax/module-reader] simply specifies
|
||||||
the @tech{module language} to be used for the language, in which case
|
the @tech{module language} to be used for the language, in which case
|
||||||
the @tech{reader} layer of the language is the same as Racket. For
|
the @tech{reader} layer of the language is the same as Racket. For
|
||||||
example, if @filepath{raquet-mlang.rkt} contains
|
example, with
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "raquet-mlang.rkt"
|
||||||
racket
|
racket
|
||||||
(provide (except-out (all-from-out racket) lambda)
|
(provide (except-out (all-from-out racket) lambda)
|
||||||
(rename-out [lambda function]))
|
(rename-out [lambda function]))
|
||||||
]
|
]
|
||||||
|
|
||||||
and @filepath{raquet.rkt} contains
|
and
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "raquet.rkt"
|
||||||
s-exp syntax/module-reader
|
s-exp syntax/module-reader
|
||||||
"raquet-mlang.rkt"
|
"raquet-mlang.rkt"
|
||||||
]
|
]
|
||||||
|
@ -194,9 +204,7 @@ access languages like @filepath{literal.rkt} and
|
||||||
@filepath{dollar-racket.rkt}. If you want to use something like
|
@filepath{dollar-racket.rkt}. If you want to use something like
|
||||||
@racket[@#,hash-lang[] literal] directly, then you must move
|
@racket[@#,hash-lang[] literal] directly, then you must move
|
||||||
@filepath{literal.rkt} into a Racket @tech{collection} named
|
@filepath{literal.rkt} into a Racket @tech{collection} named
|
||||||
@filepath{literal}, and the file @filepath{literal.rkt} must be
|
@filepath{literal}.
|
||||||
renamed to @filepath{reader.rkt} and placed in a @filepath{lang}
|
|
||||||
sub-directory of the @filepath{literal} collection.
|
|
||||||
|
|
||||||
To install a collection, you can create a directory either in the main
|
To install a collection, you can create a directory either in the main
|
||||||
Racket installation or in a user-specific directory. Use
|
Racket installation or in a user-specific directory. Use
|
||||||
|
@ -213,8 +221,21 @@ Racket installation or in a user-specific directory. Use
|
||||||
|
|
||||||
Move @filepath{literal.rkt} to @filepath{literal/lang/reader.rkt}
|
Move @filepath{literal.rkt} to @filepath{literal/lang/reader.rkt}
|
||||||
within the directory reported by @racket[find-collects-dir] or
|
within the directory reported by @racket[find-collects-dir] or
|
||||||
@racket[find-user-collects-dir]. Then, @racket[literal] can be used
|
@racket[find-user-collects-dir]. That is, the file
|
||||||
directly after @hash-lang[]:
|
@filepath{literal.rkt} must be renamed to @filepath{reader.rkt} and
|
||||||
|
placed in a @filepath{lang} sub-directory of the @filepath{literal}
|
||||||
|
collection.
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
.... @#,elem{(the main installation or the user's space)}
|
||||||
|
!- @#,filepath{collects}
|
||||||
|
!- @#,filepath{literal}
|
||||||
|
!- @#,filepath{lang}
|
||||||
|
!- @#,filepath{reader.rkt}
|
||||||
|
]
|
||||||
|
|
||||||
|
After moving the file, you can use @racket[literal] directly after
|
||||||
|
@hash-lang[]:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
@#,racket[literal]
|
@#,racket[literal]
|
||||||
|
@ -226,7 +247,7 @@ Perfect!
|
||||||
@margin-note{See @other-manual['(lib "scribblings/raco/raco.scrbl")]
|
@margin-note{See @other-manual['(lib "scribblings/raco/raco.scrbl")]
|
||||||
for more information on using @exec{raco}.}
|
for more information on using @exec{raco}.}
|
||||||
|
|
||||||
You can package up a collection for others to install by using the
|
You can also package a collection for others to install by using the
|
||||||
@exec{raco pack} command-line tool:
|
@exec{raco pack} command-line tool:
|
||||||
|
|
||||||
@commandline{raco pack --collection literal.plt literal}
|
@commandline{raco pack --collection literal.plt literal}
|
||||||
|
@ -242,7 +263,7 @@ information about @|PLaneT| packages.}
|
||||||
A better approach may be to distribute your language as a @|PLaneT|
|
A better approach may be to distribute your language as a @|PLaneT|
|
||||||
package. A drawback of using a @|PLaneT| package is that users must
|
package. A drawback of using a @|PLaneT| package is that users must
|
||||||
type @racket[@#,hash-lang[] @#,schememodname[planet]] followed by a
|
type @racket[@#,hash-lang[] @#,schememodname[planet]] followed by a
|
||||||
@|PLaneT| path to access the language. A great advantages are that the
|
@|PLaneT| path to access the language. The great advantages are that the
|
||||||
@|PLaneT| package can be installed automatically, it can be versioned,
|
@|PLaneT| package can be installed automatically, it can be versioned,
|
||||||
and it co-exists more easily with other packages.
|
and it co-exists more easily with other packages.
|
||||||
|
|
||||||
|
@ -264,10 +285,10 @@ The title of this document is ``@(get-name).''
|
||||||
}|
|
}|
|
||||||
|
|
||||||
If you put that program in DrRacket's @tech{definitions area} and
|
If you put that program in DrRacket's @tech{definitions area} and
|
||||||
click @onscreen{Run}, then nothing much appears to happen, because the
|
click @onscreen{Run}, then nothing much appears to happen. The
|
||||||
@racketmodname[scribble/base] language is similar to the
|
@racketmodname[scribble/base] language just binds and exports
|
||||||
@filepath{literal.rkt} example from @secref["hash-lang reader"]: It
|
@racketidfont{doc} as a description of a document, similar to the way
|
||||||
just binds and exports @racketidfont{doc} as a description of a document.
|
that @filepath{literal.rkt} exports a string as @racketidfont{data}.
|
||||||
|
|
||||||
Simply opening a module with the language
|
Simply opening a module with the language
|
||||||
@racketmodname[scribble/base] in DrRacket, however, causes a
|
@racketmodname[scribble/base] in DrRacket, however, causes a
|
||||||
|
@ -288,6 +309,7 @@ of a module in the @racket[literal] language as plain text instead of
|
||||||
(erroneously) as Racket syntax:
|
(erroneously) as Racket syntax:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "literal/lang/reader.rkt"
|
||||||
racket
|
racket
|
||||||
(require syntax/strip-context)
|
(require syntax/strip-context)
|
||||||
|
|
||||||
|
@ -310,7 +332,8 @@ racket
|
||||||
(lambda (key default)
|
(lambda (key default)
|
||||||
(case key
|
(case key
|
||||||
[(color-lexer)
|
[(color-lexer)
|
||||||
(dynamic-require 'syntax-color/default-lexer 'default-lexer)]
|
(dynamic-require 'syntax-color/default-lexer
|
||||||
|
'default-lexer)]
|
||||||
[else default])))
|
[else default])))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -395,44 +418,90 @@ attached to the syntax object for a parsed @racket[module] form.
|
||||||
|
|
||||||
Going back to the @racket[literal] language (see
|
Going back to the @racket[literal] language (see
|
||||||
@secref["language-get-info"]), we can adjust the language so that
|
@secref["language-get-info"]), we can adjust the language so that
|
||||||
directly running a @racket[literal] module causes it
|
directly running a @racket[literal] module causes it to print out its
|
||||||
to print out its string, while using a @racket[literal] module
|
string, while using a @racket[literal] module in a larger program
|
||||||
in a larger program simply provides @racketidfont{data}
|
simply provides @racketidfont{data} without printing. To make this
|
||||||
without printing. To make this work, we must make two changes
|
work, we will need three extra module files:
|
||||||
to @filepath{literal/lang/reader.rkt}:
|
|
||||||
|
@racketblock[
|
||||||
|
.... @#,elem{(the main installation or the user's space)}
|
||||||
|
!- @#,filepath{collects}
|
||||||
|
!- @#,filepath{literal}
|
||||||
|
!- @#,filepath{lang}
|
||||||
|
! !- @#,filepath{reader.rkt}
|
||||||
|
!- @#,filepath{language-info.rkt} @#,elem{(new)}
|
||||||
|
!- @#,filepath{runtime-config.rkt} @#,elem{(new)}
|
||||||
|
!- @#,filepath{show.rkt} @#,elem{(new)}
|
||||||
|
]
|
||||||
|
|
||||||
|
@itemlist[
|
||||||
|
|
||||||
|
@item{The @filepath{literal/language-info.rkt} module provides
|
||||||
|
reflective information about the language of modules written in
|
||||||
|
the @racket[literal] language. The name of this module is not
|
||||||
|
special; it will be connected to the @racket[literal] language
|
||||||
|
through a change to @filepath{literal/lang/reader.rkt}.}
|
||||||
|
|
||||||
|
@item{The @filepath{literal/runtime-config.rkt} module will be
|
||||||
|
identified by @filepath{literal/language-info.rkt} as the
|
||||||
|
run-time configuration code for a main module that uses the
|
||||||
|
@racket[literal] language.}
|
||||||
|
|
||||||
|
@item{The @filepath{literal/show.rkt} module will provide a
|
||||||
|
@racketidfont{show} function to be applied to the string
|
||||||
|
content of a @racket[literal] module. The run-time
|
||||||
|
configuration action in @filepath{literal/runtime-config.rkt}
|
||||||
|
will instruct @racketidfont{show} to print the strings that it
|
||||||
|
is given, but only when a module using the @racket[literal]
|
||||||
|
language is run directly.}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
Multiple modules are needed to implement the printing change, because
|
||||||
|
the different modules must run at different times. For example, the
|
||||||
|
code needed to parse a @racket[literal] module is not needed after the
|
||||||
|
module has been compiled, while the run-time configuration code is
|
||||||
|
needed only when the module is run as the main module of a
|
||||||
|
program. Similarly, when creating a stand-alone executable with
|
||||||
|
@exec{raco exe}, the main module (in compiled form) must be queried
|
||||||
|
for its run-time configuration, but the module and its configuration
|
||||||
|
action should not run until the executable is started. By using
|
||||||
|
different modules for these different tasks, we avoid loading code at
|
||||||
|
times when it is not needed.
|
||||||
|
|
||||||
|
The three new files are connected to the @racket[literal] language by
|
||||||
|
changes to @filepath{literal/lang/reader.rkt}:
|
||||||
|
|
||||||
@itemlist[
|
@itemlist[
|
||||||
|
|
||||||
@item{The @racket[module] form generated by the
|
@item{The @racket[module] form generated by the
|
||||||
@racketidfont{read-syntax} function must import a
|
@racketidfont{read-syntax} function must import the
|
||||||
@racket[literal/show] module and call its @racketidfont{show}
|
@racket[literal/show] module and call its @racketidfont{show}
|
||||||
function, which will print the given string if output has been
|
function.}
|
||||||
enabled.}
|
|
||||||
|
|
||||||
@item{The @racket[module] form must be annotated with a
|
@item{The @racket[module] form must be annotated with a
|
||||||
@racket['language-info] syntax property, whose value points to
|
@racket['language-info] syntax property, whose value points to
|
||||||
a @racketidfont{get-language-info} function exported by a
|
a @racketidfont{get-language-info} function exported by a
|
||||||
@racket[literal/language-info] module. The
|
@racket[literal/language-info] module. The
|
||||||
@racketidfont{get-language-info} function will be responsible
|
@racketidfont{get-language-info} function will be responsible
|
||||||
for reporting the runtime-configuration action of the
|
for reporting the @racket[literal/runtime-config] as the
|
||||||
language. Finally, the runtime-configuration action will turn
|
run-time configuration action of the language.
|
||||||
on printing in @racket[literal/show].}
|
|
||||||
|
The @racket['language-info] syntax property value is a vector
|
||||||
|
that contains a module (in this case
|
||||||
|
@racket[literal/language-info]), a symbol for one of the
|
||||||
|
module's exports (@racketidfont{get-language-info} in this
|
||||||
|
case), and an data value (which is not needed in this
|
||||||
|
case). The data component allows information to be propagated
|
||||||
|
from the source to the module's language information.}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
The @racketidfont{get-language-info} function is not attached to the
|
|
||||||
@racket[module] form directly; instead, the function is identified by
|
|
||||||
a module path and a symbol, so that the function can be loaded when it
|
|
||||||
is needed. Those two parts are combined in a vector along with a third
|
|
||||||
part, which is an argument to supply to the
|
|
||||||
@racketidfont{get-language-info} function (in case extra information
|
|
||||||
needs to be propagated from the source to the module's language
|
|
||||||
information).
|
|
||||||
|
|
||||||
These changes are implemented in the following revised
|
These changes are implemented in the following revised
|
||||||
@filepath{literal/lang/reader.rkt}:
|
@filepath{literal/lang/reader.rkt}:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "literal/lang/reader.rkt"
|
||||||
racket
|
racket
|
||||||
(require syntax/strip-context)
|
(require syntax/strip-context)
|
||||||
|
|
||||||
|
@ -460,7 +529,8 @@ racket
|
||||||
(lambda (key default)
|
(lambda (key default)
|
||||||
(case key
|
(case key
|
||||||
[(color-lexer)
|
[(color-lexer)
|
||||||
(dynamic-require 'syntax-color/default-lexer 'default-lexer)]
|
(dynamic-require 'syntax-color/default-lexer
|
||||||
|
'default-lexer)]
|
||||||
[else default])))
|
[else default])))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -478,6 +548,7 @@ For @racket[literal], @filepath{literal/language-info.rkt} is
|
||||||
implemented as:
|
implemented as:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "literal/language-info.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(provide get-language-info)
|
(provide get-language-info)
|
||||||
|
@ -493,17 +564,12 @@ racket
|
||||||
The function returned by @racketidfont{get-language-info} answers a
|
The function returned by @racketidfont{get-language-info} answers a
|
||||||
@racket['configure-runtime] query with a list of yet more vectors,
|
@racket['configure-runtime] query with a list of yet more vectors,
|
||||||
where each vector contains a module name, an exported name, and a data
|
where each vector contains a module name, an exported name, and a data
|
||||||
value. This indirection through another set of vectors (as opposed to
|
value. For the @racket[literal] language, the run-time configuration
|
||||||
performing run-time configurations directly) allows the action to be
|
action implemented in @filepath{literal/runtime-config.rkt} is to
|
||||||
delayed beyond the query time. For example, run-time configuration
|
enable printing of strings that are sent to @racketidfont{show}:
|
||||||
actions may need to collected to create a stand-alone executable, but
|
|
||||||
the actions must be performed only when the executable is launched.
|
|
||||||
|
|
||||||
For the @racket[literal] language, the run-time configuration action
|
|
||||||
implemented in @filepath{literal/runtime-config.rkt} is to enable
|
|
||||||
printing of strings that are sent to @racketidfont{show}:
|
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "literal/runtime-config.rkt"
|
||||||
racket
|
racket
|
||||||
(require "show.rkt")
|
(require "show.rkt")
|
||||||
|
|
||||||
|
@ -518,6 +584,7 @@ the @racketidfont{show-enabled} parameter and @racketidfont{show}
|
||||||
function:
|
function:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "literal/runtime-config.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(provide show show-enabled)
|
(provide show show-enabled)
|
||||||
|
@ -534,20 +601,15 @@ following variant of @filepath{tuvalu.rkt} directly and through a
|
||||||
@scheme[require] from another module:
|
@scheme[require] from another module:
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "tuvalu.rkt"
|
||||||
@#,racket[literal]
|
@#,racket[literal]
|
||||||
Technology!
|
Technology!
|
||||||
System!
|
System!
|
||||||
Perfect!
|
Perfect!
|
||||||
]
|
]
|
||||||
|
|
||||||
Customizing the @racket[literal] language required many different
|
When using @racketmodname[syntax/module-reader] to implement a
|
||||||
modules, because different modules are needed to keep the different
|
language, specify a module's language information through the
|
||||||
phases and times of different tasks separate. For example, the code
|
@racket[#:language-info] optional specification. The value provided
|
||||||
needed to correctly color @racket[literal] text in DrRacket should not
|
through @racket[#:language-info] is attached to a @racket[module] form
|
||||||
be required to simply run a @racket[literal] program.
|
|
||||||
|
|
||||||
The @racketmodname[syntax/module-reader] language lets you specify a
|
|
||||||
module's language information through a @racket[#:language-info]
|
|
||||||
optional specification. The value provided through
|
|
||||||
@racket[#:language-info] is attached to a @racket[module] form
|
|
||||||
directly as a syntax property.
|
directly as a syntax property.
|
||||||
|
|
|
@ -25,4 +25,4 @@
|
||||||
null
|
null
|
||||||
(cons (replace-context #'file v)
|
(cons (replace-context #'file v)
|
||||||
(loop)))))))])
|
(loop)))))))])
|
||||||
#'(racketmod content ...)))]))
|
#'(racketmod #:file file content ...)))]))
|
||||||
|
|
|
@ -29,12 +29,12 @@ language} that is a variant of @racketmodname[racket]:
|
||||||
(module raquet racket
|
(module raquet racket
|
||||||
(provide (except-out (all-from-out racket) lambda)
|
(provide (except-out (all-from-out racket) lambda)
|
||||||
(rename-out [lambda function])))
|
(rename-out [lambda function])))
|
||||||
(module tennis 'raquet
|
(module score 'raquet
|
||||||
(map (function (points) (case points
|
(map (function (points) (case points
|
||||||
[(0) "love"] [(1) "fifteen"]
|
[(0) "love"] [(1) "fifteen"]
|
||||||
[(2) "thirty"] [(3) "forty"]))
|
[(2) "thirty"] [(3) "forty"]))
|
||||||
(list 0 2)))
|
(list 0 2)))
|
||||||
(require 'tennis)
|
(require 'score)
|
||||||
]
|
]
|
||||||
|
|
||||||
@; ----------------------------------------
|
@; ----------------------------------------
|
||||||
|
@ -65,7 +65,7 @@ as @tech{module language}:
|
||||||
|
|
||||||
The other implicit forms provided by @racket[racket/base] are
|
The other implicit forms provided by @racket[racket/base] are
|
||||||
@racket[#%app] for function calls, @racket[#%datum] for literals, and
|
@racket[#%app] for function calls, @racket[#%datum] for literals, and
|
||||||
@racket[#%top] for unbound identifiers:
|
@racket[#%top] for identifiers that have no binding:
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
(module just-lambda racket
|
(module just-lambda racket
|
||||||
|
@ -77,7 +77,7 @@ The other implicit forms provided by @racket[racket/base] are
|
||||||
(require 'ten)
|
(require 'ten)
|
||||||
]
|
]
|
||||||
|
|
||||||
Implicit forms like @racket[#%app] can be used explicitly in a module,
|
Implicit forms such as @racket[#%app] can be used explicitly in a module,
|
||||||
but they exist mainly to allow a module language to restrict or change
|
but they exist mainly to allow a module language to restrict or change
|
||||||
the meaning of implicit uses. For example, a @racket[lambda-calculus]
|
the meaning of implicit uses. For example, a @racket[lambda-calculus]
|
||||||
@tech{module language} might restrict functions to a single argument,
|
@tech{module language} might restrict functions to a single argument,
|
||||||
|
@ -91,7 +91,7 @@ unbound identifiers as uninterpreted symbols:
|
||||||
[1-arg-app #%app]
|
[1-arg-app #%app]
|
||||||
[1-form-module-begin #%module-begin]
|
[1-form-module-begin #%module-begin]
|
||||||
[no-literals #%datum]
|
[no-literals #%datum]
|
||||||
[unbound-as-self #%top]))
|
[unbound-as-quoted #%top]))
|
||||||
(define-syntax-rule (1-arg-lambda (x) expr)
|
(define-syntax-rule (1-arg-lambda (x) expr)
|
||||||
(lambda (x) expr))
|
(lambda (x) expr))
|
||||||
(define-syntax-rule (1-arg-app e1 e2)
|
(define-syntax-rule (1-arg-app e1 e2)
|
||||||
|
@ -100,7 +100,7 @@ unbound identifiers as uninterpreted symbols:
|
||||||
(#%module-begin e))
|
(#%module-begin e))
|
||||||
(define-syntax (no-literals stx)
|
(define-syntax (no-literals stx)
|
||||||
(raise-syntax-error #f "no" stx))
|
(raise-syntax-error #f "no" stx))
|
||||||
(define-syntax-rule (unbound-as-self . id)
|
(define-syntax-rule (unbound-as-quoted . id)
|
||||||
'id))
|
'id))
|
||||||
(module ok 'lambda-calculus
|
(module ok 'lambda-calculus
|
||||||
((lambda (x) (x z))
|
((lambda (x) (x z))
|
||||||
|
@ -179,6 +179,6 @@ s-exp "html.rkt"
|
||||||
(p "Updated: " ,(now))
|
(p "Updated: " ,(now))
|
||||||
]
|
]
|
||||||
|
|
||||||
The later section @secref["hash-languages"] explains how to define
|
Later in this guide, @secref["hash-languages"] explains how to define
|
||||||
your own @hash-lang[] language, but first we explain how you can write
|
your own @hash-lang[] language, but first we explain how you can write
|
||||||
@tech{reader}-level extensions to Racket.
|
@tech{reader}-level extensions to Racket.
|
||||||
|
|
|
@ -28,6 +28,7 @@ parsed as determined by the @racketidfont{read} and
|
||||||
For example, suppose that file @filepath{five.rkt} contains
|
For example, suppose that file @filepath{five.rkt} contains
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
|
#:file "five.rkt"
|
||||||
racket/base
|
racket/base
|
||||||
|
|
||||||
(provide read read-syntax)
|
(provide read read-syntax)
|
||||||
|
|
|
@ -41,13 +41,13 @@ re-exports some variables from the linked units for further linking.
|
||||||
The interface of a unit is described in terms of
|
The interface of a unit is described in terms of
|
||||||
@deftech{signatures}. Each signature is defined (normally within a
|
@deftech{signatures}. Each signature is defined (normally within a
|
||||||
@racket[module]) using @racket[define-signature]. For example, the
|
@racket[module]) using @racket[define-signature]. For example, the
|
||||||
following signature, placed in a @filepath{toy-factory-sig.ss} file,
|
following signature, placed in a @filepath{toy-factory-sig.rkt} file,
|
||||||
describes the exports of a component that implements a toy factory:
|
describes the exports of a component that implements a toy factory:
|
||||||
|
|
||||||
@margin-note{By convention, signature names with @litchar{^}.}
|
@margin-note{By convention, signature names with @litchar{^}.}
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"toy-factory-sig.ss"
|
"toy-factory-sig.rkt"
|
||||||
racket]
|
racket]
|
||||||
|
|
||||||
(define-signature toy-factory^
|
(define-signature toy-factory^
|
||||||
|
@ -66,10 +66,10 @@ using @racket[define-unit] with an @racket[export] clause that names
|
||||||
@margin-note{By convention, unit names with @litchar["@"].}
|
@margin-note{By convention, unit names with @litchar["@"].}
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"simple-factory-unit.ss"
|
"simple-factory-unit.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "toy-factory-sig.ss")]
|
(require "toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define-unit simple-factory@
|
(define-unit simple-factory@
|
||||||
(import)
|
(import)
|
||||||
|
@ -97,7 +97,7 @@ for the sake of an example with interesting features, that the store
|
||||||
is willing to sell only toys in a particular color.)
|
is willing to sell only toys in a particular color.)
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"toy-store-sig.ss"
|
"toy-store-sig.rkt"
|
||||||
racket]
|
racket]
|
||||||
|
|
||||||
(define-signature toy-store^
|
(define-signature toy-store^
|
||||||
|
@ -109,11 +109,11 @@ racket]
|
||||||
]
|
]
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"toy-store-unit.ss"
|
"toy-store-unit.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "toy-store-sig.ss"
|
(require "toy-store-sig.rkt"
|
||||||
"toy-factory-sig.ss")]
|
"toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define-unit toy-store@
|
(define-unit toy-store@
|
||||||
(import toy-factory^)
|
(import toy-factory^)
|
||||||
|
@ -139,9 +139,9 @@ racket
|
||||||
(provide toy-store@)
|
(provide toy-store@)
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that @filepath{toy-store-unit.ss} imports
|
Note that @filepath{toy-store-unit.rkt} imports
|
||||||
@filepath{toy-factory-sig.ss}, but not
|
@filepath{toy-factory-sig.rkt}, but not
|
||||||
@filepath{simple-factory-unit.ss}. Consequently, the
|
@filepath{simple-factory-unit.rkt}. Consequently, the
|
||||||
@racket[toy-store@] unit relies only on the specification of a toy
|
@racket[toy-store@] unit relies only on the specification of a toy
|
||||||
factory, not on a specific implementation.
|
factory, not on a specific implementation.
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ The @racket[simple-factory@] unit has no imports, so it can be
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "simple-factory-unit.ss") (void))
|
(eval:alts (require "simple-factory-unit.rkt") (void))
|
||||||
(invoke-unit simple-factory@)
|
(invoke-unit simple-factory@)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ to produce @racket[toy-store^]:
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "toy-store-unit.ss") (void))
|
(eval:alts (require "toy-store-unit.rkt") (void))
|
||||||
(define-values/invoke-unit/infer toy-store@)
|
(define-values/invoke-unit/infer toy-store@)
|
||||||
(get-inventory)
|
(get-inventory)
|
||||||
(stock! 2)
|
(stock! 2)
|
||||||
|
@ -206,10 +206,10 @@ repainted. Instead, the toys are always created using the store's
|
||||||
color, which the factory gets by importing @racket[toy-store^]:
|
color, which the factory gets by importing @racket[toy-store^]:
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"store-specific-factory-unit.ss"
|
"store-specific-factory-unit.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "toy-factory-sig.ss")]
|
(require "toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define-unit store-specific-factory@
|
(define-unit store-specific-factory@
|
||||||
(import toy-store^)
|
(import toy-store^)
|
||||||
|
@ -243,7 +243,7 @@ unit's imports using the exports of other linked units.
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "store-specific-factory-unit.ss") (void))
|
(eval:alts (require "store-specific-factory-unit.rkt") (void))
|
||||||
(define-compound-unit/infer toy-store+factory@
|
(define-compound-unit/infer toy-store+factory@
|
||||||
(import)
|
(import)
|
||||||
(export toy-factory^ toy-store^)
|
(export toy-factory^ toy-store^)
|
||||||
|
@ -306,11 +306,11 @@ that creates a toy store in a @racket[lambda] to supply the store's
|
||||||
color:
|
color:
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"toy-store-maker.ss"
|
"toy-store-maker.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "toy-store-sig.ss"
|
(require "toy-store-sig.rkt"
|
||||||
"toy-factory-sig.ss")]
|
"toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define toy-store@-maker
|
(define toy-store@-maker
|
||||||
(lambda (the-color)
|
(lambda (the-color)
|
||||||
|
@ -346,9 +346,9 @@ To invoke a unit created by @racket[toy-store@-maker], we must use
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "simple-factory-unit.ss") (void))
|
(eval:alts (require "simple-factory-unit.rkt") (void))
|
||||||
(define-values/invoke-unit/infer simple-factory@)
|
(define-values/invoke-unit/infer simple-factory@)
|
||||||
(eval:alts (require "toy-store-maker.ss") (void))
|
(eval:alts (require "toy-store-maker.rkt") (void))
|
||||||
(define-values/invoke-unit (toy-store@-maker 'purple)
|
(define-values/invoke-unit (toy-store@-maker 'purple)
|
||||||
(import toy-factory^)
|
(import toy-factory^)
|
||||||
(export toy-store^))
|
(export toy-store^))
|
||||||
|
@ -370,7 +370,7 @@ To link a unit from @racket[toy-store@-maker], we can use the
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "store-specific-factory-unit.ss") (void))
|
(eval:alts (require "store-specific-factory-unit.rkt") (void))
|
||||||
(define toy-store+factory@
|
(define toy-store+factory@
|
||||||
(compound-unit
|
(compound-unit
|
||||||
(import)
|
(import)
|
||||||
|
@ -409,13 +409,13 @@ produced by the expression.
|
||||||
|
|
||||||
@section{Whole-@racket[module] Signatures and Units}
|
@section{Whole-@racket[module] Signatures and Units}
|
||||||
|
|
||||||
In programs that use units, modules like @filepath{toy-factory-sig.ss}
|
In programs that use units, modules like @filepath{toy-factory-sig.rkt}
|
||||||
and @filepath{simple-factory-unit.ss} are common. The
|
and @filepath{simple-factory-unit.rkt} are common. The
|
||||||
@racket[racket/signature] and @racket[racket/unit] module names can be
|
@racket[racket/signature] and @racket[racket/unit] module names can be
|
||||||
used as languages to avoid much of the boilerplate module, signature,
|
used as languages to avoid much of the boilerplate module, signature,
|
||||||
and unit declaration text.
|
and unit declaration text.
|
||||||
|
|
||||||
For example, @filepath{toy-factory-sig.ss} can be written as
|
For example, @filepath{toy-factory-sig.rkt} can be written as
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
racket/signature
|
racket/signature
|
||||||
|
@ -427,15 +427,15 @@ toy-color (code:comment #, @tt{(toy? -> symbol?)})
|
||||||
]
|
]
|
||||||
|
|
||||||
The signature @racket[toy-factory^] is automatically provided from the
|
The signature @racket[toy-factory^] is automatically provided from the
|
||||||
module, inferred from the filename @filepath{toy-factory-sig.ss} by
|
module, inferred from the filename @filepath{toy-factory-sig.rkt} by
|
||||||
replacing the @filepath{-sig.ss} suffix with @racketidfont{^}.
|
replacing the @filepath{-sig.rkt} suffix with @racketidfont{^}.
|
||||||
|
|
||||||
Similarly, @filepath{simple-factory-unit.ss} module can be written
|
Similarly, @filepath{simple-factory-unit.rkt} module can be written
|
||||||
|
|
||||||
@racketmod[
|
@racketmod[
|
||||||
racket/unit
|
racket/unit
|
||||||
|
|
||||||
(require "toy-factory-sig.ss")
|
(require "toy-factory-sig.rkt")
|
||||||
|
|
||||||
(import)
|
(import)
|
||||||
(export toy-factory^)
|
(export toy-factory^)
|
||||||
|
@ -453,8 +453,8 @@ racket/unit
|
||||||
]
|
]
|
||||||
|
|
||||||
The unit @racket[simple-factory@] is automatically provided from the
|
The unit @racket[simple-factory@] is automatically provided from the
|
||||||
module, inferred from the filename @filepath{simple-factory-unit.ss} by
|
module, inferred from the filename @filepath{simple-factory-unit.rkt} by
|
||||||
replacing the @filepath{-unit.ss} suffix with @racketidfont["@"].
|
replacing the @filepath{-unit.rkt} suffix with @racketidfont["@"].
|
||||||
|
|
||||||
@; ----------------------------------------
|
@; ----------------------------------------
|
||||||
|
|
||||||
|
@ -474,7 +474,7 @@ of the @racket[toy-factory^] signature adds the contracts previously
|
||||||
written in comments:
|
written in comments:
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"contracted-toy-factory-sig.ss"
|
"contracted-toy-factory-sig.rkt"
|
||||||
racket]
|
racket]
|
||||||
|
|
||||||
(define-signature contracted-toy-factory^
|
(define-signature contracted-toy-factory^
|
||||||
|
@ -490,10 +490,10 @@ Now we take the previous implementation of @racket[simple-factory@] and
|
||||||
implement this version of @racket[toy-factory^] instead:
|
implement this version of @racket[toy-factory^] instead:
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"contracted-simple-factory-unit.ss"
|
"contracted-simple-factory-unit.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "contracted-toy-factory-sig.ss")]
|
(require "contracted-toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define-unit contracted-simple-factory@
|
(define-unit contracted-simple-factory@
|
||||||
(import)
|
(import)
|
||||||
|
@ -519,7 +519,7 @@ causes the appropriate contract errors.
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "contracted-simple-factory-unit.ss") (void))
|
(eval:alts (require "contracted-simple-factory-unit.rkt") (void))
|
||||||
(define-values/invoke-unit/infer contracted-simple-factory@)
|
(define-values/invoke-unit/infer contracted-simple-factory@)
|
||||||
(build-toys 3)
|
(build-toys 3)
|
||||||
(build-toys #f)
|
(build-toys #f)
|
||||||
|
@ -539,10 +539,10 @@ implements the regular @racket[toy-factory^], but whose exports
|
||||||
have been protected with an appropriate unit contract.
|
have been protected with an appropriate unit contract.
|
||||||
|
|
||||||
@racketmod/eval[[#:file
|
@racketmod/eval[[#:file
|
||||||
"wrapped-simple-factory-unit.ss"
|
"wrapped-simple-factory-unit.rkt"
|
||||||
racket
|
racket
|
||||||
|
|
||||||
(require "toy-factory-sig.ss")]
|
(require "toy-factory-sig.rkt")]
|
||||||
|
|
||||||
(define-unit/contract wrapped-simple-factory@
|
(define-unit/contract wrapped-simple-factory@
|
||||||
(import)
|
(import)
|
||||||
|
@ -568,7 +568,7 @@ racket
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
#:eval toy-eval
|
#:eval toy-eval
|
||||||
(eval:alts (require "wrapped-simple-factory-unit.ss") (void))
|
(eval:alts (require "wrapped-simple-factory-unit.rkt") (void))
|
||||||
(define-values/invoke-unit/infer wrapped-simple-factory@)
|
(define-values/invoke-unit/infer wrapped-simple-factory@)
|
||||||
(build-toys 3)
|
(build-toys 3)
|
||||||
(build-toys #f)
|
(build-toys #f)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user