1. Introduction

This is the reference document for Sweet.js. For a gentle explaination of Sweet’s concepts see the tutorial.

2. Command Line API

  • --out-file <file>: write result to file

  • --out-dir <dir>: write result to directory

  • --no-babel: do not use babel backend

3. Binding Forms

3.1. syntax

syntax <name> = <init>

Bind <name> to the result of evaluating <init> in the compiletime environment. Scoping follows let (i.e. block scoped with a temporal dead zone).

If the result of evaluating <init> is a function, then the result is a syntax transformer.

3.2. syntaxrec

syntaxrec <name> = <init>

Identical to the syntax form except that <name> is also bound inside of <init>. This enables recursive macro definitions.

4. Syntax Transformer

transformer : (TransformerContext) -> List(Syntax)

A syntax transformer is a function bound to a compile-time name. A syntax transformer is invoked with a transformer context that provides access to the syntax at the call-site and returns a list of syntax objects.

4.1. Transformer Context

A transformer context is an iterable object that provides access to syntax at the call-site of a syntax transformer.

TransformerContext = {
  name: () -> Syntax
  next: () -> {
    done: boolean,
    value: Syntax
  }
  expand: (string) -> {
    done: boolean,
    value: Syntax
  }
  mark: () -> Marker
  reset: (Marker?) -> undefined
}

Each call to next returns the syntax object following the transformer call.

A call to expand initiates expansion at the current state of the iterator and matches the specified grammar production. Matching is "greedy" so expand('expr') with the syntax 1 + 2 matches the entire binary expression rather than just 1. The following productions are accepted by expand:

  • Statement with alias stmt

  • AssignmentExpression with alias expr

  • Expression

  • BlockStatement

  • WhileStatement

  • IfStatement

  • ForStatement

  • SwitchStatement

  • BreakStatement

  • ContinueStatement

  • DebuggerStatement

  • WithStatement

  • TryStatement

  • ThrowStatement

  • ClassDeclaration

  • FunctionDeclaration

  • LabeledStatement

  • VariableDeclarationStatement

  • ReturnStatement

  • ExpressionStatement

  • YieldExpression

  • ClassExpression

  • ArrowExpression

  • NewExpression

  • ThisExpression

  • FunctionExpression

  • IdentifierExpression

  • LiteralNumericExpression

  • LiteralInfinityExpression

  • LiteralStringExpression

  • TemplateExpression

  • LiteralBooleanExpression

  • LiteralNullExpression

  • LiteralRegExpExpression

  • ObjectExpression

  • ArrayExpression

  • UnaryExpression

  • UpdateExpression

  • BinaryExpression

  • StaticMemberExpression

  • ComputedMemberExpression

  • AssignmentExpression

  • CompoundAssignmentExpression

  • ConditionalExpression

The name() method returns the syntax object of the macro name at the macro invocation site. This is useful[1] because it allows a macro transformer to get access to the lexical context at the invocation site.

A call to mark returns a pointer to the current state of the iterator.

Calling reset with no arguments returns the context to its initial state, while passing a Marker instance returns the context to the state pointed to by the marker.

syntax m = function (ctx) {
  ctx.expand('expr');
  ctx.reset();

  const a = ctx.next().value;
  ctx.next();

  const marker = ctx.mark();
  ctx.expand('expr');
  ctx.reset(marker);

  const b = ctx.next().value;
  return #`${a} + ${b} + 24`; // 30 + 42 + 24
};
m 30 + 42 + 66

5. Syntax Objects

Syntax objects represent the syntax from the source program. While syntax objects have internal structure, that structure is intentionally undocumented and subject to change (in fact, major change to syntax objects is planned for the next major version).

Introspection and manipulation functions are provided in the helpers library documented in the following section.

6. Helpers

A library of helper functions for introspecting syntax objects is provided at 'sweet.js/helpers'. To use inside a macro definition, import for syntax.

import { isStringLiteral } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  return isStringLiteral(ctx.next().value) ? #`'a string'` : #`'not a string'`;
};
m 'foo'
(expansion)
'a string'

The helper library contains the following kinds of functions:

  • is* functions that test the type of a syntax object (e.g. isIdentifier for identifiers and isStringLiteral for strings)

  • from* functions that create new syntax objects from primitive data

  • an unwrap function that returns the primitive representation of a syntax object

6.1. unwrap

unwrap(stx: any): {
  value?: string | number | List<Syntax>
}

In the case of a flat syntax object (i.e. not delimiters), unwrap returns an object with a single value property that holds the primitive representation of that piece of syntax (a string for string literals, keywords, and identifiers or a number for numeric literals).

For syntax objects that represent delimiters, unwrap returns an object who’s value property is a list of the syntax objects inside the delimiter.

For all other inputs unwrap returns the empty object.

import { unwrap } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let id = ctx.next().value;
  let delim = ctx.next().value;

  unwrap(id).value === 'foo';  // true
  let num = unwrap(delim).value.get(1);
  unwrap(num).value === 1;     // true
  // ...
};
m foo (1)

6.2. fromIdentifier

fromIdentifier(other: Syntax, s: string): Syntax

Create a new identifier syntax object named s using the lexical context from other.

import { fromIdentifier } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let dummy = #`dummy`.get(0);

  return #`${fromIdentifier(dummy, 'bar')}`;
};
m foo
(expansion)
bar
Note

Be careful which syntax object you use to create a new syntax object via fromIdentifier and related functions since the new object will share the original’s lexical context. In most cases you will want to create a "dummy" syntax object inside a macro definition and then use that as a base to create new objects. By using a dummy syntax object you are using the scope of the macro definition; usually the macro definition scope is what you want.

You may be tempted to reuse the syntax object provided by ctx.name() but resist that feeling! The ctx.name() syntax object comes from the macro call-site and so any syntax objects created from it will carry the lexical context of the call-site. Sometimes this is what you want, but most of the time this breaks hygiene!

6.3. fromNumber

fromNumber(other: Syntax, n: number): Syntax

Create a new numeric literal syntax object with the value n using the lexical context from other.

import { fromNumber } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let dummy = #`dummy`.get(0);

  return #`${fromNumber(dummy, 1)}`;
};
m
(expansion)
1

6.4. fromString

fromString(other: Syntax, s: string): Syntax

Create a new string literal syntax object with the value s using the lexical context from other.

import { unwrap, fromString } from 'sweet.js/helpers' for syntax;

syntax to_str = ctx => {
  let dummy = #`dummy`.get(0);
  let arg = unwrap(ctx.next().value).value;
  return #`${fromString(dummy, arg)}`;
}
to_str foo
(expansion)
'foo'

6.5. fromPunctuator

fromPunctuator(other: Syntax, punc: string): Syntax

Creates a punctuator (e.g. +, ==, etc.) from its string representation punc using the lexical context from other.

import { fromPunctuator } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let dummy = #`dummy`.get(0);
  return #`1 ${fromPunctuator(dummy, '+')} 1`;
};
m
(expansion)
1 + 1

6.6. fromKeyword

fromKeyword(other: Syntax, kwd: string): Syntax

Create a new keyword syntax object with the value kwd using the lexical context from other.

syntax m = ctx => {
  let dummy = #`dummy`.get(0);
  return #`${dummy.fromKeyword('let')} x = 1`;
};
m
(expansion)
let x = 1

6.7. fromBraces

fromBraces(other: Syntax, inner: List<Syntax>): Syntax

Creates a curly brace delimiter with inner syntax objects inner using the lexical context from other.

import { fromBraces } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let dummy = #`dummy`.get(0);
  let block = #`let x = 1;`;
  return #`${fromBraces(dummy, block)}`;
};
m
(expansion)
{
  let x = 1;
}

6.8. fromBrackets

fromBrackets(other: Syntax, inner: List<Syntax>): Syntax

Creates a bracket delimiter with inner syntax objects inner using the lexical context from other.

syntax m = ctx => {
  let dummy = #`dummy`.get(0);
  let elements = #`1, 2, 3`;
  return #`${fromBrackets(dummy, elements)}`;
};
m
(expansion)
[1, 2, 3]

6.9. fromParens

fromParens(other: Syntax, inner: List<Syntax>): Syntax

creates a paren delimiter with inner syntax objects inner using the lexical context from other.

import { fromParens } from 'sweet.js/helpers' for syntax;

syntax m = ctx => {
  let dummy = #`dummy`.get(0);
  let expr = #`5 * 5`;
  return #`1 + ${fromParens(dummy, expr)}`;
};
m
(expansion)
1 + (5 * 5)

6.10. isIdentifier

isIdentifier(s: Syntax): boolean

Returns true if the syntax object is an identifier.

6.11. isNumericLiteral

isNumericLiteral(s: Syntax): boolean

Returns true if the syntax object is a numeric literal.

6.12. isStringLiteral

isStringLiteral(s: Syntax): boolean

Returns true if the syntax object is a string literal.

6.13. isKeyword

isKeyword(s: Syntax): boolean

Returns true if the syntax object is a keyword.

6.14. isPunctuator

isPunctuator(s: Syntax): boolean

Returns true if the syntax object is a puncuator.

6.15. isTemplate

isTemplate(s: Syntax): boolean

Returns true if the syntax object is a template literal.

6.16. isSyntaxTemplate

isSyntaxTemplate(s: Syntax): boolean

Returns true if the syntax object is a syntax template literal.

6.17. isParens

isParens(s: Syntax): boolean

Returns true if the syntax object is a parenthesis delimiter (e.g. ( …​ )).

6.18. isBrackets

isBrackets(s: Syntax): boolean

Returns true if the syntax object is a bracket delimiter (e.g. [ …​ ]).

6.19. isBraces

isBraces(s: Syntax): boolean

Returns true if the syntax object is a braces delimiter (e.g. { …​ }).

7. Syntax Templates

Syntax templates construct a list of syntax objects from a literal representation using backtick (#`foo bar baz`). They are similar to ES2015 templates but with the special sweet.js specific # template tag.

Syntax templates support interpolations just like normal templates via ${…​}:

syntax m = function (ctx) {
  return #`${ctx.next().value} + 24`;
};
m 42

The expressions inside an interpolation must evaluate to a syntax object, an array, a list, or an transformer context.


1. or will become useful as more features are implemented in Sweet