Sew
1 The Sew Language
1.1 Sew Directives
8<-plan-from-here
2 The Sew Built Language
2.1 Sew Built Source Directives
2.2 Sew Built Mid-Stream Commands
8.12

Sew🔗ℹ

Sew is a library for declaring the language of a Racket file in a more expressive way than just a one-line #lang declaration. This longer "declaration" can essentially frontload the boilerplate of the file, leaving the rest of the file to be expressed in a way that’s more straightforward for its application domain.

Sew comes with two Racket languages, #lang sew for use in most #lang racket-like languages to express boilerplate that gets added on the syntax object level, and #lang sew/built for use in files which have been generated by a text-manipulating build step, to help these generated files associate themselves with the source locations the maintainer actually wants to see in error reports. Using both these techniques at once can make it possible to express certain boilerplate entirely in the build step, factoring it out pervasively from the source representation of the repo, while the code distributed in the Racket package registry still resembles the code maintainers see aside from having some file headers.

    1 The Sew Language

      1.1 Sew Directives

    2 The Sew Built Language

      2.1 Sew Built Source Directives

      2.2 Sew Built Mid-Stream Commands

1 The Sew Language🔗ℹ

 #lang sew package: sew-lib

The #lang sew language makes it easy to add boilerplate that surrounds the rest of the syntax objects in the file, without changing its indentation.

#lang sew racket/base
 
(require (only-in sew 8<-plan-from-here))
 
[8<-plan-from-here [<> ...]
  #'(begin
      (provide main)
 
      (define (main)
        <> ...))]
 
(displayln "Hello, world!")

The expression in the body of the 8<-plan-from-here form is evaluated in phase 1, with <> bound as a template variable to the rest of the content of the file.

This can come in handy for maintaining the file as though it belongs to a certain tradition of writing modules, while it actually belongs to another. For instance, the module above can be written as though it’s a script that’s executed one line after another, when it’s actually a module that only executes that behavior when a provided main function is called.

Some other potential uses:

For now, 8<-plan-from-here is the only directive defined for use in #lang sew. We might extend Sew in the future to allow files to be cut up into more than one piece before they’re all sewn together. This may resemble literate programming techniques for assembling programs in terms of chunks.

1.1 Sew Directives🔗ℹ

 (require sew) package: sew-lib

syntax

[8<-plan-from-here rest-of-file-pattern preprocess-expr]

This must be used at the top level of a module that uses #lang sew. It parses the rest of the forms in the module according to the syntax/parse pattern rest-of-file-pattern and executes preprocess-expr in the syntax phase. The result of that expression is then used as the replacement (expansion) of this form and all the rest of the forms in the file.

For instance, the usage site

[8<-plan-from-here [<> ...]
  #'(begin
      (provide main)
 
      (define (main)
        <> ...))]
 
(displayln "Hello, world!")

expands into this:

(begin
  (provide main)
 
  (define (main)
    (displayln "Hello, world!")))

2 The Sew Built Language🔗ℹ

 #lang sew/built package: sew-lib

The #lang sew/built language provides a way to associate the surface text of a module with a different source location than it usually would be associated with. This can help attribute error information to appropriate places when Racket module files are generated by a build process or a Racket-targeting compiler/transpiler.

As a simple example, suppose we want to write code like this:

(my-fancy-displayln "Hello, world!")

And suppose we actually want it to behave as though it were written like this:

#lang racket/base
 
(require "my-util.rkt")
 
(my-fancy-displayln "Hello, world!")

A build script could simply insert the #lang and require lines as it copies our source file to a distributable directory, but that may not be satisfactory. With such a simplistic build step, if the my-fancy-displayln call has an error, that error will be reported in terms of line 5 of the generated file, rather than line 1 of the file we actually need to modify.

Instead, we can generate code that uses #lang sew/built:

#lang sew/built racket/base
 
(require "my-util.rkt")
 
[#8<-source "my-project codebase /src/my-fancy-hello-world.rkt"]
[#8<-set-port-next-location! 1 0 1]
[#8<-disregard-further-commands]
(my-fancy-displayln "Hello, world!")

This way, the source location reported for errors having to do with the my-fancy-displayln call will be reported as line 1, column 0, byte position 1 of the source "my-project codebase /src/my-fancy-hello-world.rkt".

Since #lang sew can frontload other kinds of boilerplate in Racket programs, there can be a nice synergy between #lang sew and #lang sew/built:

#lang sew/built sew racket/base
[#8<-source "my-project codebase /src/my-hello-world.rkt"]
 
(require (only-in sew 8<-plan-from-here))
 
[8<-plan-from-here [<> ...]
  #'(begin
      (provide main)
 
      (define (main)
        <> ...))]
 
[#8<-set-port-next-location! 1 0 1]
[#8<-disregard-further-commands]
(displayln "Hello, world!")

For now, Sew Built defines a small number of source directives and mid-stream commands, and there’s currently no way to extend this.

In detail, a Sew Built module is read using the following process:

First, a form is read using the default Racket reader extended so that #8< can begin a symbol. This form is used as a directive to indicate the source of the module, and it must be a correct use of #8<-authentic-source, #8<-authentic-source, or #8<-build-path-source.

Then, the rest of the module is read using the original #lang’s reader but using a modified input port. This input port forwards along any text it finds until a space, tab, carriage return, newline, form feed, (, [, or {. When it finds one of those, it waits until it can determine whether or not the input is in the form (#8<, [#8<, or {#8< with any number of preceding spaces, tabs, newlines, carriage returns, or form feed characters. If the input is not in such a form, it’s passed along as usual. If it is in that form, the input port reads a syntax object using the default Racket reader extended so that #8< can begin a symbol. Then, it consumes and discards any number of spaces or tabs it finds, and if there is a carriage return, a newline, or a carriage return newline (CRLF) sequence, it consumes that as well. Then the syntax object it read this way is interpreted as a Sew Built mid-stream command, and it must be a correct use of #8<-set-port-next-location!, #8<-write-string, or #8<-disregard-further-commands.

Note that since Sew Built mid-stream commands are recognized by the sequence of an opening bracket directly followed by #8<, comments and whitespace cannot appear in between the bracket and the #8<. This makes the syntax of mid-stream commands slightly different from the syntax of source directives. Both of these differ from Sew directives like [8<-plan-from-here ...]. The resemblance between these three syntaxes is mainly for the sake of superficial visual cohesion of the Sew library as a whole, with the presence or absence of # conveying that something out of the ordinary is going on at read time.

Note also that since we’re discarding so much whitespace, a non-whitespace token may appear split into two non-whitespace tokens in the source even if it is still ultimately treated as a single token.

2.1 Sew Built Source Directives🔗ℹ

Sew Built source directive

[#8<-authentic-source]

Specifies that the source of this module is the one it would usually be if this were any other language but Sew Built. (This would be a nice default, but the source directive of a Sew Built module is not optional.)

Like other Sew Built source directives, this may only be used at the very beginning of a module that uses #lang sew/built.

Sew Built source directive

[#8<-source source-string]

Specifies that the source of this module is the given source-string.

The source-string form must be a literal string. This condition may be loosened in the future to allow for other kinds of sources, since any object passed to read-syntax can be a source. Usually, the source for a Racket module is a path object; however, the main use for it is to render it to a string in an error report. For modules that are compiled from sources not actually in the distribution users are running, a short description like "<library name> codebase <path within library>" may be ideal.

Like other Sew Built source directives, this may only be used at the very beginning of a module that uses #lang sew/built.

Sew Built source directive

[#8<-build-path-source subpath-string]

Specifies that the source of this module is a path, namely the path constructible with build-path and simplify-path using the given subpath-string interpreted relative to the path this module would usually have as its source.

If the usual source the module would have is neither a path-string? nor a path-for-some-system?, then the subpath-string is just used as the source directly rather than resolving it.

The subpath-string form must be a literal string, and it must satisfy path-string? and yet not be a complete-path?.

Like other Sew Built source directives, this may only be used at the very beginning of a module that uses #lang sew/built.

2.2 Sew Built Mid-Stream Commands🔗ℹ

Sew Built mid-stream command

[#8<-set-port-next-location! line column position]

Sets the next source location in the input stream as though using set-port-next-location!.

The line form must be a literal exact-positive-integer?. It represents the line number, with 1 representing a position within the first line of the file.

The column form must be a literal exact-nonnegative-integer?. It represents the column number, with 0 representing the position before the first character in the current line.

The position form must be a literal exact-positive-integer?. It represents the byte position in the file, with 1 representing the position before the first byte of the file.

Like other Sew Built mid-stream commands, this may be used anywhere after the source directive of a module that uses #lang sew/built. As with other Sew Built mid-stream commands, certain whitespace characters before the command are consumed and discarded, and certain whitespace after the command, up to and including the first encountered newline (if any), is consumed and discarded as well.

Sew Built mid-stream command

[#8<-write-string input-string]

Sets up the input stream to read input-string next, as though using write-string on an output port that feeds into the language reader’s input port. The input-string form must be a literal string.

This can be used to escape instances of syntax that looks like the beginning of a Sew Built mid-stream command. In particular, it can be useful to use [#8<-write-string "#"] to escape instances of # within #8<, and it can also be used to escape whitespace sequences around other commands, since those would otherwise be discarded.

Like other Sew Built mid-stream commands, this may be used anywhere after the source directive of a module that uses #lang sew/built. As with other Sew Built mid-stream commands, certain whitespace characters before the command are consumed and discarded, and certain whitespace after the command, up to and including the first encountered newline (if any), is consumed and discarded as well.

Sew Built mid-stream command

[#8<-disregard-further-commands]

Causes the rest of the input stream to be processed without detecting or executing any further Sew Built mid-stream commands.

This is good for creating build processes that maintain the illusion that Sew Built was never used. Without it, maintainers who accidentally write something that looks like a Sew Build mid-stream command might find their build output failing or misbehaving.

Like other Sew Built mid-stream commands, this may be used anywhere after the source directive of a module that uses #lang sew/built. As with other Sew Built mid-stream commands, certain whitespace characters before the command are consumed and discarded, and certain whitespace after the command, up to and including the first encountered newline (if any), is consumed and discarded as well.