Skip to content

Adding a New Form to the Language

Siddhartha Kasivajhula edited this page Jan 14, 2025 · 5 revisions

Adding a new form to the language entails, at a high level:

  1. the syntax and semantics of the new form
  2. tests
  3. documentation, both user and, if applicable, developer
  4. benchmarks, both local and, if applicable, nonlocal

Syntax and Semantics

Adding a Macro

Add the macro definition in flow/extended/forms.rkt using one of the usual Qi macro definition forms.

Adding a Deforestable list operation

Add the macro definition in flow/list.rkt using define-deforestable.

Adding a Core Form

  1. Add the form grammar to the syntax specification in flow/extended/expander.rkt.
  2. If there is just one variant of the syntax, add the pattern and the code generation template to the qi0->racket macro (in flow/core/compiler/1000-qi0.rkt) directly.
  3. If there is more than one variant, write a syntax class (in flow/core/syntax.rkt) to match these patterns, and use it in a single qi0->racket clause, delegating to a parser defined in the same file that provides templates for each pattern.
  4. If the implementation is nontrivial, favor writing a runtime function in flow/core/runtime.rkt and using that in the template (this reduces the generated code size -- TODO: elaborate on precisely how reducing code size helps performance. Is it just memory use? Or also running time? Does it help only compilation performance? Or also runtime performance?).

Adding a Qi function

You may want to implement your form simply as a "Qi function," that is, a function bound in the Qi binding space, but otherwise identical to Racket functions. There may be a few different reasons for doing this -- either to provide an optimized runtime for a built-in Qi macro (see A Loophole in Qi Space), or just as a standard library function, the way that Racket provides many standard library functions.

To do the former, add the implementation to flow/extended/runtime.rkt. For the latter, we don't yet have any examples of this (though perhaps range would be a good candidate?), so there are no conventions for this at the moment.

Tests

Add tests in the appropriate modules in qi-test. Consult the organization of test modules to get an idea of which components are affected and where adding tests would be advisable.

Documentation

Start by documenting the form in qi-doc/scribblings/forms.scrbl. See Writing Qi Docs for guidelines on fully integrating the new form into the docs.

Benchmarks

  1. Write local benchmarks in qi-sdk/benchmarks/local
  2. If you're adding a core form and if there are optimizations involved (e.g., deforestation), write a new benchmark (if applicable) in qi-sdk/benchmarks/competitive. These are rigorous benchmarks that use the vlibench package and are run on every commit on the main branch, but as they also take time to run, only add a benchmark here if it reveals something interesting about your new form that isn't already demonstrated in the other benchmarks. For example, we don't have benchmarks for list-ref because, asymptotically, the behavior is no different from car, which we do benchmark.
  3. Also for when optimizations are involved, write "smoke" nonlocal benchmarks in qi-sdk/benchmarks/nonlocal. These are not rigorous, but they help during development to show rough performance changes, and should be added for every form.
Clone this wiki locally