Preserves Schemas for Racket

Today has mostly been spent working on Preserves Schemas.

I made a few changes to the Schema language itself, ancillary changes to the TypeScript/JavaScript implementation, and built a first pass at a Racket implementation of a Schema compiler.

Schema language changes

  • It’s now forbidden to use both “&” (intersection) and “/” (alternation) operators in a single rule. You have to pick one or the other. If you want to use both, you have to lift the inner one out to a separate rule.

        ; Not allowed:
        BadRule = A / B & C / D .
    
        ; Allowed:
        GoodRule = A / BC / D .
        BC = B & C .
    

    Rationale: It was confusing that there was precedence there at all; and if both operators are available at the same time, there’s an ambiguous case with respect to the names chosen for the branches vs the names chosen for the branches of an intersection. Here’s what the code used to say about this:

    1
    2
    3
    4
    
    // TODO: deal with situation where there's an or of ands, where
    // the branches of the and are named. The parsing is ambiguous, and
    // with the current code I think (?) you end up with the same name
    // attached to the or-branch as to the leftmost and-branch.
    

Metaschema changes

Implementation and tooling changes

  • Some “type checking” of Schemas is now performed at “read” time, not just at code-generation time. This means that all toolchains that use readSchema from reader.ts automatically benefit from checkSchema.

    Concretely, at the moment checkSchema does duplicate-binding checks and also ensures that a Schema specifies a bijection between plain Preserves content and the parsed data structures specified by the Schema.

  • I built an initial Preserves Schema compiler/code-generator for Racket. So far, it is able to read and generate code for working with the metaschema.

    Here’s an example. This definition from the metaschema:

    Schema = <schema {
      version: Version
      embeddedType: EmbeddedTypeName
      definitions: Definitions
    }>.
    

    generates the following code (as part of a complete module for all the definitions in the metaschema):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    (struct Schema (version embeddedType definitions) #:prefab)
    
    (define (parse-Schema input)
      (match input
        [(and dest
              (record 'schema
                      (list
                       (hash-table
                        ('version (app parse-Version (and $version (not (== eof)))))
                        ('embeddedType (app parse-EmbeddedTypeName (and $embeddedType (not (== eof)))))
                        ('definitions (app parse-Definitions (and $definitions (not (== eof)))))
                        (_ _) ...))))
         (Schema $version $embeddedType $definitions)]
        [_ eof]))
    
    (define (Schema->preserves input)
      (match input
        [(Schema $version $embeddedType $definitions)
         (record 'schema
                 (list
                  (hash
                   'version (Version->preserves $version)
                   'embeddedType (EmbeddedTypeName->preserves $embeddedType)
                   'definitions (Definitions->preserves $definitions))))]))