Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RiffDB Contract Language Reference v26

This is the complete grammar-version-26 parser source. Grammar v26 adds contextual mutate first selection under statically proved pool ownership. Normative semantics, bounds, compatibility gates, and the prohibition on general loops and raw transactions are defined by ADR-0002 as amended by ADR-0015, ADR-0107, ADR-0109, ADR-0133, ADR-0136, ADR-0153, ADR-0173, ADR-0256, ADR-0262, and ADR-0264. Semantic actions below construct only the source-oriented AST; they are not typed IR, an executable plan, or a durable encoding.

Complete Grammar

use crate::ast::*;
use crate::diagnostic::SyntaxDiagnostic;
use crate::lexer::Token;
use crate::parser::{self, ParsedExpression};
use crate::span::Spanned;

grammar;

pub ContractDocument: ContractDocument = {
    <contract:ContractDeclaration> => ContractDocument { contract },
};

ContractDeclaration: Spanned<Contract> = {
    <lo:@L> "contract" <name:Identifier> "version" <version:UnsignedInteger>
        "{" <declarations:Declaration*> "}" <hi:@R>
        => parser::spanned(Contract { name, version, declarations }, lo, hi),
};

Declaration: Spanned<Declaration> = {
    <lo:@L> <entity:EntityDeclaration> <hi:@R>
        => parser::spanned(Declaration::Entity(entity), lo, hi),
    <lo:@L> <event:EventDeclaration> <hi:@R>
        => parser::spanned(Declaration::Event(event), lo, hi),
    <lo:@L> <enumeration:EnumDeclaration> <hi:@R>
        => parser::spanned(Declaration::Enum(enumeration), lo, hi),
    <lo:@L> <aggregate:AggregateDeclaration> <hi:@R>
        => parser::spanned(Declaration::Aggregate(aggregate), lo, hi),
    <lo:@L> <command:CommandDeclaration> <hi:@R>
        => parser::spanned(Declaration::Command(command), lo, hi),
    <lo:@L> <workflow:WorkflowDeclaration> <hi:@R>
        => parser::spanned(Declaration::Workflow(workflow), lo, hi),
    <lo:@L> <projection:ProjectionDeclaration> <hi:@R>
        => parser::spanned(Declaration::Projection(projection), lo, hi),
    <lo:@L> <columnar:ColumnarProjectionDeclaration> <hi:@R>
        => parser::spanned(Declaration::ColumnarProjection(columnar), lo, hi),
    <lo:@L> <fact:PrincipalFactDeclaration> <hi:@R>
        => parser::spanned(Declaration::PrincipalFact(fact), lo, hi),
    <lo:@L> <policy:RowPolicyDeclaration> <hi:@R>
        => parser::spanned(Declaration::RowPolicy(policy), lo, hi),
};

PrincipalFactDeclaration: PrincipalFactDeclaration = {
    "principal" "fact" <name:Identifier> ":" <ty:TypeExpression>
        => PrincipalFactDeclaration { name, ty },
};

RowPolicyDeclaration: RowPolicyDeclaration = {
    "row" "policy" <name:Identifier> "on" <entity:Identifier>
        "{" <rules:RowPolicyRule+> "}"
        => RowPolicyDeclaration { name, entity, rules },
};

RowPolicyRule: Spanned<RowPolicyRule> = {
    <lo:@L> "allow" <operation:RowPolicyOperation> "when"
        <expression:RowPolicyExpression> <hi:@R>
        => parser::spanned(RowPolicyRule { operation, expression: expression.into_syntax() }, lo, hi),
};

RowPolicyOperation: Spanned<RowPolicyOperation> = {
    <lo:@L> "read" <hi:@R> => parser::spanned(RowPolicyOperation::Read, lo, hi),
    <lo:@L> "create" <hi:@R> => parser::spanned(RowPolicyOperation::Create, lo, hi),
    <lo:@L> "update" <hi:@R> => parser::spanned(RowPolicyOperation::Update, lo, hi),
    <lo:@L> "delete" <hi:@R> => parser::spanned(RowPolicyOperation::Delete, lo, hi),
};

RowPolicyExpression: parser::ParsedRowPolicyExpression = RowPolicyOrExpression;

RowPolicyOrExpression: parser::ParsedRowPolicyExpression = {
    <left:RowPolicyAndExpression> <rest:(<OrOperator> <RowPolicyAndExpression>)*>
        =>? parser::grammar_result(parser::row_policy_binary_chain(left, rest)),
};

RowPolicyAndExpression: parser::ParsedRowPolicyExpression = {
    <left:RowPolicyPredicate> <rest:(<AndOperator> <RowPolicyPredicate>)*>
        =>? parser::grammar_result(parser::row_policy_binary_chain(left, rest)),
};

RowPolicyPredicate: parser::ParsedRowPolicyExpression = {
    <left:RowPolicyUnary> <operator:ComparisonOperator> <right:RowPolicyUnary>
        =>? parser::grammar_result(parser::row_policy_binary(left, operator, right)),
    <needle:RowPolicyUnary> "in" <haystack:RowPolicyUnary>
        =>? parser::grammar_result(parser::row_policy_in(needle, haystack)),
    <value:RowPolicyUnary> "is" <negated:("not")?> "null"
        =>? parser::grammar_result(parser::row_policy_is_null(value, negated.is_some())),
    RowPolicyUnary,
};

RowPolicyUnary: parser::ParsedRowPolicyExpression = {
    <lo:@L> "!" <value:RowPolicyUnary> <hi:@R>
        =>? parser::grammar_result(parser::row_policy_not(value, parser::span(lo, hi))),
    RowPolicyPrimary,
};

RowPolicyPrimary: parser::ParsedRowPolicyExpression = {
    <literal:Literal> => parser::row_policy_literal(literal),
    <path:RowPolicyPath> => parser::row_policy_path(path),
    <lo:@L> "(" <expression:RowPolicyExpression> ")" <hi:@R>
        =>? parser::grammar_result(parser::row_policy_parenthesized(expression, parser::span(lo, hi))),
    <lo:@L> "exists" <entity:Identifier> "." <index:Identifier>
        "(" <arguments:RowPolicyArgumentList> ")" <hi:@R>
        =>? parser::grammar_result(parser::row_policy_exists(entity, index, arguments, parser::span(lo, hi))),
};

RowPolicyArgumentList: Vec<parser::ParsedRowPolicyExpression> = {
    <head:RowPolicyExpression> <tail:("," <RowPolicyExpression>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

RowPolicyPath: Spanned<Path> = {
    <lo:@L> <head:RowPolicyPathSegment> <tail:("." <RowPolicyPathSegment>)*> <hi:@R> => {
        let mut segments = vec![head];
        segments.extend(tail);
        parser::spanned(Path { segments }, lo, hi)
    },
};

RowPolicyPathSegment: Spanned<String> = {
    Identifier,
};

WorkflowDeclaration: WorkflowDeclaration = {
    "workflow" <name:Identifier> "{"
        "entity" <entity:Identifier>
        "state" <state_field:Identifier>
        <initial_state:("initial" <Identifier>)?>
        <transitions:WorkflowTransitionDeclaration+>
        <lease:WorkflowLeaseDeclaration?>
    "}" => WorkflowDeclaration { name, entity, state_field, initial_state, transitions, lease },
};

WorkflowTransitionDeclaration: Spanned<WorkflowTransitionDeclaration> = {
    <lo:@L> "transition" <name:Identifier> "from" "(" <source_states:IdentifierList> ")"
        "to" <destination:Identifier> <hi:@R>
        => parser::spanned(WorkflowTransitionDeclaration {
            name,
            source_states,
            destination,
        }, lo, hi),
};

WorkflowLeaseDeclaration: Spanned<WorkflowLeaseDeclaration> = {
    <lo:@L> "lease" <name:Identifier> "{"
        "owner" <owner_field:Identifier>
        "expires_at" <expiry_field:Identifier>
        "fencing_token" <fencing_token_field:Identifier>
        <attempt_field:WorkflowLeaseAttempts?>
        "duration_seconds" "(" <minimum_duration_seconds:UnsignedInteger> ","
            <maximum_duration_seconds:UnsignedInteger> ")"
    "}" <hi:@R> => parser::spanned(WorkflowLeaseDeclaration {
        name,
        owner_field,
        expiry_field,
        fencing_token_field,
        attempt_field,
        minimum_duration_seconds,
        maximum_duration_seconds,
    }, lo, hi),
};

WorkflowLeaseAttempts: Spanned<String> = {
    "attempts" <field:Identifier> => field,
};

EntityDeclaration: EntityDeclaration = {
    "entity" <name:Identifier> "{" <items:EntityItem*> "}"
        => EntityDeclaration { name, items },
};

EntityItem: Spanned<EntityItem> = {
    <lo:@L> "key" "(" <fields:TypedFieldList> ")" <hi:@R>
        => parser::spanned(EntityItem::Key(KeyDeclaration { fields }), lo, hi),
    <lo:@L> "field" <field:TypedField> <hi:@R>
        => parser::spanned(EntityItem::Field(FieldDeclaration {
            secret: None,
            name: field.name,
            ty: field.ty,
        }), lo, hi),
    // Contextual: the classification modifier is an ordinary identifier
    // everywhere except between `field` and the field name, so `field
    // secret: ...` stays a valid field named `secret` (ADR-0118 additive
    // compatibility).
    <lo:@L> "field" <modifier:Identifier> <field:TypedField> <hi:@R>
        =>? parser::grammar_result(parser::classified_field_item(modifier, field, lo, hi)),
    <lo:@L> <invariant:InvariantDeclaration> <hi:@R>
        => parser::spanned(EntityItem::Invariant(invariant), lo, hi),
    <lo:@L> "index" <name:Identifier> "(" <fields:IdentifierList> ")"
        <options:IndexOption*> <hi:@R>
        => parser::spanned(EntityItem::Index(IndexDeclaration { name, fields, options }), lo, hi),
    <lo:@L> "unique" <name:Identifier> "(" <fields:IdentifierList> ")" <hi:@R>
        => parser::spanned(EntityItem::Unique(UniqueDeclaration { name, fields }), lo, hi),
    <lo:@L> "reference" <name:Identifier> "(" <source_fields:IdentifierList> ")"
        "->" <target_entity:Identifier> "(" <target_fields:IdentifierList> ")" <hi:@R>
        => parser::spanned(EntityItem::Reference(ReferenceDeclaration {
            name,
            source_fields,
            target_entity,
            target_fields,
        }), lo, hi),
    <lo:@L> "vector_field" <name:Identifier> "(" <dimension:UnsignedInteger> ","
        <metric:VectorMetric> "," "(" <source_fields:IdentifierList> ")" ","
        <slo_keyword:Identifier> <staleness_slo:UnsignedInteger>
        <production:VectorProductionClause?>
        <ann:VectorAnnClause?> ")" <hi:@R>
        =>? parser::grammar_result(parser::vector_field_item(
            name,
            dimension,
            metric,
            source_fields,
            slo_keyword,
            staleness_slo,
            production,
            ann,
            lo,
            hi,
        )),
    <lo:@L> "text_index" <name:Identifier> "(" "(" <source_fields:TextIndexSourceList> ")"
        "," <analyzer_keyword:Identifier> <analyzer:Identifier>
        "," <slo_keyword:Identifier> <staleness_slo:UnsignedInteger>
        "," <age_keyword:Identifier> <replay_age_seconds:UnsignedInteger>
        "," <bytes_keyword:Identifier> <replay_bytes:UnsignedInteger>
        "," <backlog_keyword:Identifier> <replay_backlog:UnsignedInteger>
        "," <result_keyword:Identifier> <result_model:Identifier>
        "," <terms_keyword:Identifier> <max_terms:UnsignedInteger>
        "," <candidates_keyword:Identifier> <max_candidates:UnsignedInteger>
        "," <results_keyword:Identifier> <max_results:UnsignedInteger>
        ")" <hi:@R>
        =>? parser::grammar_result(parser::text_index_item(
            name,
            source_fields,
            analyzer_keyword,
            analyzer,
            slo_keyword,
            staleness_slo,
            age_keyword,
            replay_age_seconds,
            bytes_keyword,
            replay_bytes,
            backlog_keyword,
            replay_backlog,
            result_keyword,
            result_model,
            terms_keyword,
            max_terms,
            candidates_keyword,
            max_candidates,
            results_keyword,
            max_results,
            lo,
            hi,
        )),
    <lo:@L> "pattern_index" <name:Identifier> "(" <field:Identifier>
        "," <profile_keyword:Identifier> <profile:Identifier>
        "," <operators_keyword:Identifier> "(" <operators:IdentifierList> ")"
        "," <bounds:LongPatternBounds> ")" <hi:@R>
        =>? parser::grammar_result(parser::long_pattern_item(
            name,
            field,
            profile_keyword,
            profile,
            operators_keyword,
            operators,
            bounds,
            lo,
            hi,
        )),
    <lo:@L> "delete_policy" "no_inbound" <hi:@R>
        => parser::spanned(EntityItem::DeletePolicy(DeletePolicyDeclaration::NoInbound), lo, hi),
    <lo:@L> "delete_policy" "restrict" <source_entity:Identifier> "." <index:Identifier> <hi:@R>
        => parser::spanned(EntityItem::DeletePolicy(DeletePolicyDeclaration::Restrict {
            source_entity,
            index,
        }), lo, hi),
    <lo:@L> "delete_policy" "cascade" "{" <relationships:CascadeRelationship+> "}" <hi:@R>
        => parser::spanned(EntityItem::DeletePolicy(DeletePolicyDeclaration::Cascade {
            relationships,
        }), lo, hi),
};

LongPatternBounds: Vec<LongPatternBoundDeclaration> = {
    <head:LongPatternBound> <tail:("," <LongPatternBound>)*> => {
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

LongPatternBound: LongPatternBoundDeclaration = {
    <name:Identifier> <value:UnsignedInteger> => LongPatternBoundDeclaration { name, value },
};

TextIndexSourceList: Vec<Spanned<TextIndexSourceDeclaration>> = {
    => Vec::new(),
    <head:TextIndexSource> <tail:("," <TextIndexSource>)*> <_trailing:Comma?> => {
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

TextIndexSource: Spanned<TextIndexSourceDeclaration> = {
    <lo:@L> <field:Identifier> <weight_keyword:Identifier> <weight:UnsignedInteger> <hi:@R>
        => parser::spanned(TextIndexSourceDeclaration { field, weight_keyword, weight }, lo, hi),
};

VectorProductionClause: VectorProductionDeclaration = {
    "," <model_keyword:Identifier> <model_identity:StringLiteral>
        "," <version_keyword:Identifier> <current_model_version:StringLiteral>
        "," <age_keyword:Identifier> <replay_age_seconds:UnsignedInteger>
        "," <bytes_keyword:Identifier> <replay_bytes:UnsignedInteger>
        "," <backlog_keyword:Identifier> <replay_backlog:UnsignedInteger>
        =>? parser::grammar_result(parser::vector_production_declaration(
            model_keyword,
            model_identity,
            version_keyword,
            current_model_version,
            age_keyword,
            replay_age_seconds,
            bytes_keyword,
            replay_bytes,
            backlog_keyword,
            replay_backlog,
        )),
};

CascadeRelationship: Spanned<CascadeRelationshipDeclaration> = {
    <lo:@L> "relationship" <source_entity:Identifier> "." <relationship:Identifier>
        "using" <index_entity:Identifier> "." <index:Identifier>
        "maximum" <maximum:UnsignedInteger> <hi:@R>
        => parser::spanned(CascadeRelationshipDeclaration {
            source_entity,
            relationship,
            index_entity,
            index,
            maximum,
        }, lo, hi),
};

VectorAnnClause: VectorAnnDeclaration = {
    "," <threshold_keyword:Identifier> <row_threshold:UnsignedInteger>
        "," <recall_keyword:Identifier> <recall_target_bps:UnsignedInteger>
        =>? parser::grammar_result(parser::vector_ann_declaration(
            threshold_keyword,
            row_threshold,
            recall_keyword,
            recall_target_bps,
        )),
};

IndexOption: Spanned<IndexOption> = {
    <lo:@L> "cover" "(" <fields:IdentifierList> ")" <hi:@R>
        => parser::spanned(IndexOption::Cover { fields }, lo, hi),
    <lo:@L> "presence" "(" <field:Identifier> ")" <hi:@R>
        => parser::spanned(IndexOption::Presence { field }, lo, hi),
    <lo:@L> "text_key" "(" <field:Identifier> "," <profile:TextKeyProfile> ")" <hi:@R>
        => parser::spanned(IndexOption::TextKey { field, profile }, lo, hi),
};

TextKeyProfile: Spanned<TextKeyProfile> = {
    <lo:@L> "binary_utf8_v1" <hi:@R>
        => parser::spanned(TextKeyProfile::BinaryUtf8V1, lo, hi),
    <lo:@L> "unicode_fold_v1" <hi:@R>
        => parser::spanned(TextKeyProfile::UnicodeFoldV1, lo, hi),
};

// Contextual: the metric names and `staleness_slo` are ordinary identifiers
// everywhere except inside a vector_field declaration, so `field cosine: ...`
// stays valid (the WP-591 hard keywords broke that additively-claimed
// compatibility).
VectorMetric: Spanned<VectorMetricKeyword> = {
    <metric:Identifier> =>? parser::grammar_result(parser::vector_metric(metric)),
};

EventDeclaration: EventDeclaration = {
    "event" <name:Identifier> "{" <partition_by:EventPartition?>
        <policy_anchor:EventPolicyAnchor?> <fields:SpannedTypedField*> "}"
        => EventDeclaration { name, partition_by, policy_anchor, fields },
};

EventPartition: Spanned<Vec<Spanned<String>>> = {
    <lo:@L> "partition_by" "(" <fields:IdentifierList> ")" <hi:@R>
        => parser::spanned(fields, lo, hi),
};

EventPolicyAnchor: Spanned<EventPolicyAnchorDeclaration> = {
    <lo:@L> "policy_anchor" <current:Identifier> <entity:Identifier>
        "(" <fields:EventPolicyAnchorFieldList> ")" <hi:@R> =>? {
            parser::grammar_result(parser::event_policy_anchor(current, entity, fields, parser::span(lo, hi)))
        },
};

EventPolicyAnchorFieldList: Vec<Spanned<EventPolicyAnchorField>> = {
    <head:EventPolicyAnchorField> <tail:("," <EventPolicyAnchorField>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

EventPolicyAnchorField: Spanned<EventPolicyAnchorField> = {
    <lo:@L> <entity_field:Identifier> ":" <payload_field:Identifier> <hi:@R>
        => parser::spanned(EventPolicyAnchorField { entity_field, payload_field }, lo, hi),
};

EnumDeclaration: EnumDeclaration = {
    "enum" <name:Identifier> "{" <variants:IdentifierList> "}"
        => EnumDeclaration { name, variants },
};

AggregateDeclaration: AggregateDeclaration = {
    "aggregate" <name:Identifier> "{" <items:AggregateItem*> "}"
        => AggregateDeclaration { name, items },
};

AggregateItem: Spanned<AggregateItem> = {
    <lo:@L> "root" <name:Identifier> <hi:@R>
        => parser::spanned(AggregateItem::Root(name), lo, hi),
    <lo:@L> "child" <name:Identifier> <hi:@R>
        => parser::spanned(AggregateItem::Child(name), lo, hi),
    <lo:@L> "partition_by" <expression:Expression> <hi:@R>
        => parser::spanned(AggregateItem::PartitionBy(expression.into_syntax()), lo, hi),
    <lo:@L> "conflict_key" "(" <expressions:ExpressionList> ")" <hi:@R>
        => parser::spanned(AggregateItem::ConflictKey(expressions), lo, hi),
    <lo:@L> <invariant:InvariantDeclaration> <hi:@R>
        => parser::spanned(AggregateItem::Invariant(invariant), lo, hi),
};

InvariantDeclaration: InvariantDeclaration = {
    "invariant" <name:Identifier> ":" <expression:Expression>
        => InvariantDeclaration { name, expression: expression.into_syntax() },
};

TypedField: TypedField = {
    <name:Identifier> ":" <ty:TypeExpression> => TypedField { name, ty },
};

SpannedTypedField: Spanned<TypedField> = {
    <lo:@L> <field:TypedField> <hi:@R> => parser::spanned(field, lo, hi),
};

TypedFieldList: Vec<Spanned<TypedField>> = {
    <head:SpannedTypedField> <tail:("," <SpannedTypedField>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

IdentifierList: Vec<Spanned<String>> = {
    <head:Identifier> <tail:("," <Identifier>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

TypeExpression: Spanned<TypeExpression> = {
    <lo:@L> "bool" <hi:@R> => parser::spanned(TypeExpression::Bool, lo, hi),
    <lo:@L> "i64" <hi:@R> => parser::spanned(TypeExpression::I64, lo, hi),
    <lo:@L> "u64" <hi:@R> => parser::spanned(TypeExpression::U64, lo, hi),
    <lo:@L> "timestamp" <hi:@R> => parser::spanned(TypeExpression::Timestamp, lo, hi),
    <lo:@L> "date" <hi:@R> => parser::spanned(TypeExpression::Date, lo, hi),
    <lo:@L> "uuid" <hi:@R> => parser::spanned(TypeExpression::Uuid, lo, hi),
    <lo:@L> "decimal" "<" <precision:UnsignedInteger> "," <scale:UnsignedInteger> ">" <hi:@R>
        => parser::spanned(TypeExpression::Decimal { precision, scale }, lo, hi),
    <lo:@L> "money" "<" <currency:Identifier> ">" <hi:@R>
        =>? parser::grammar_result(parser::currency_type(currency, parser::span(lo, hi))),
    <lo:@L> "string" "<" <maximum:UnsignedInteger> ">" <hi:@R>
        => parser::spanned(TypeExpression::String { maximum }, lo, hi),
    <lo:@L> "bytes" "<" <maximum:UnsignedInteger> ">" <hi:@R>
        => parser::spanned(TypeExpression::Bytes { maximum }, lo, hi),
    <lo:@L> "vector" "<" <dimension:UnsignedInteger> ">" <hi:@R>
        => parser::spanned(TypeExpression::Vector { dimension }, lo, hi),
    <lo:@L> "optional" "<" <inner:TypeExpression> ">" <hi:@R>
        => parser::spanned(TypeExpression::Optional(Box::new(inner)), lo, hi),
    <lo:@L> "list" "<" <element:TypeExpression> "," <bound:ListBound> ">" <hi:@R>
        => parser::spanned(TypeExpression::List {
            element: Box::new(element),
            minimum: bound.0,
            maximum: bound.1,
        }, lo, hi),
    <lo:@L> <name:Identifier> <hi:@R>
        => parser::spanned(TypeExpression::Named(name), lo, hi),
};

ListBound: (Option<Spanned<String>>, Spanned<String>) = {
    <maximum:UnsignedInteger> => (None, maximum),
    <lo:@L> <range:"unsigned integer range"> <hi:@R> => {
        let (minimum, maximum) = range;
        (Some(parser::spanned(minimum, lo, hi)), parser::spanned(maximum, lo, hi))
    },
};

CommandDeclaration: CommandDeclaration = {
    <kind:ApplicationCommandKind> <name:Identifier> "{"
        <inputs:InputDeclaration*>
        <service_values:ServiceValueDeclaration*>
        <idempotency:IdempotencyClause?>
        <bindings:Binding*>
        <decisions:CommandDecision*>
        <bulk_iteration:BulkIteration?>
        <requirements:Requirement*>
        <effects:Effect*>
        <return_clause:ReturnClause>
    "}" => CommandDeclaration {
        kind,
        name,
        inputs,
        service_values,
        idempotency,
        bindings,
        decisions,
        bulk_iteration,
        reconstitution: None,
        requirements,
        effects,
        return_clause,
    },
    "reimport" "command" <name:Identifier> "{"
        <inputs:InputDeclaration+>
        <reconstitution:ReconstitutionClause>
        <return_clause:ReturnClause>
    "}" => CommandDeclaration {
        kind: CommandKind::Reimport,
        name,
        inputs,
        service_values: Vec::new(),
        idempotency: None,
        bindings: Vec::new(),
        decisions: Vec::new(),
        bulk_iteration: None,
        reconstitution: Some(reconstitution),
        requirements: Vec::new(),
        effects: Vec::new(),
        return_clause,
    },
};

ApplicationCommandKind: CommandKind = {
    "command" => CommandKind::Ordinary,
    "bulk" "command" => CommandKind::Bulk,
};

ReconstitutionClause: Spanned<ReconstitutionClause> = {
    <lo:@L> "reconstitute" <entity:Identifier> "from" <source:Identifier>
        "else" <failure:OutcomeExpression> <hi:@R>
        => parser::spanned(ReconstitutionClause { entity, source, failure }, lo, hi),
};

BulkIteration: Spanned<BulkIteration> = {
    <lo:@L> "for" <element:Identifier> "in" <collection:Identifier> "{"
        <bindings:Binding*>
        <decisions:CommandDecision*>
        <requirements:Requirement*>
        <effects:Effect*>
    "}" <hi:@R> => parser::spanned(BulkIteration {
        element,
        collection,
        bindings,
        decisions,
        requirements,
        effects,
    }, lo, hi),
};

CommandDecision: Spanned<CommandDecision> = {
    <lo:@L> "observe_or_initialize" <binding:InitializedEntityBinding>
        "decide" <subject:Identifier> "{"
            <when_arms:DecisionWhenArm+>
            <else_action:DecisionElseArm>
        "}" <hi:@R>
        => parser::spanned(CommandDecision {
            binding,
            subject,
            when_arms,
            else_action,
        }, lo, hi),
};

DecisionWhenArm: Spanned<DecisionArm> = {
    <lo:@L> "when" <predicate:Expression> "=>" <action:DecisionAction> <hi:@R>
        => parser::spanned(DecisionArm {
            predicate: predicate.into_syntax(),
            action,
        }, lo, hi),
};

DecisionElseArm: Spanned<DecisionAction> = {
    "else" "=>" <action:DecisionAction> => action,
};

DecisionAction: Spanned<DecisionAction> = {
    <lo:@L> "apply" "{" <bindings:Binding*> <effects:Effect*> "}" <hi:@R>
        => parser::spanned(DecisionAction::Apply { bindings, effects }, lo, hi),
    <lo:@L> "no_effect" <hi:@R>
        => parser::spanned(DecisionAction::NoEffect, lo, hi),
    <lo:@L> "reject" <outcome:OutcomeExpression> <hi:@R>
        => parser::spanned(DecisionAction::Reject(outcome), lo, hi),
};

ServiceValueDeclaration: Spanned<ServiceValueDeclaration> = {
    <lo:@L> "service" <name:Identifier> ":" <kind:ServiceValueKind> <hi:@R>
        => parser::spanned(ServiceValueDeclaration { name, kind }, lo, hi),
};

ServiceValueKind: Spanned<ServiceValueKind> = {
    <lo:@L> "uuid_v7" <hi:@R> => parser::spanned(ServiceValueKind::UuidV7, lo, hi),
    <lo:@L> "transaction_time" <hi:@R>
        => parser::spanned(ServiceValueKind::TransactionTime, lo, hi),
};

InputDeclaration: Spanned<InputDeclaration> = {
    <lo:@L> "input" <field:TypedField>
        <aggregate_bytes:("aggregate_bytes" "<=" <UnsignedInteger>)?> <hi:@R>
        => parser::spanned(InputDeclaration { field, aggregate_bytes }, lo, hi),
};

IdempotencyClause: Spanned<IdempotencyClause> = {
    <lo:@L> "idempotency_key" <expression:Expression> <hi:@R>
        => parser::spanned(IdempotencyClause { expression: expression.into_syntax() }, lo, hi),
};

Binding: Spanned<Binding> = {
    <lo:@L> "mutate" <modifier:Identifier> <entity:Identifier> "." <index:Identifier>
        "(" <arguments:ExpressionList> ")" "as" <binding:Identifier> "else" <failure:OutcomeExpression> <hi:@R>
        =>? parser::grammar_result(parser::selected_binding(modifier, EntityBinding {
            selection_index: Some(index), entity, arguments, binding, failure,
            restriction_failure: None, cascade_failure: None,
        }, lo, hi)),
    <lo:@L> "read" <binding:EntityBinding> <hi:@R>
        => parser::spanned(Binding::Read(binding), lo, hi),
    <lo:@L> "mutate" <binding:EntityBinding> <hi:@R>
        => parser::spanned(Binding::Mutate(binding), lo, hi),
    <lo:@L> "create" <binding:EntityBinding> <hi:@R>
        => parser::spanned(Binding::Create(binding), lo, hi),
    <lo:@L> "init_or_mutate" <binding:InitializedEntityBinding> <hi:@R>
        => parser::spanned(Binding::InitOrMutate(binding), lo, hi),
    <lo:@L> "delete" <binding:DeleteEntityBinding> <hi:@R>
        => parser::spanned(Binding::Delete(binding), lo, hi),
};

InitializedEntityBinding: InitializedEntityBinding = {
    <entity:Identifier> "(" <arguments:ExpressionList> ")"
        "as" <binding:Identifier> "initialize" <initializer:ObjectLiteral>
        => InitializedEntityBinding {
            entity,
            arguments,
            binding,
            initializer,
        },
};

EntityBinding: EntityBinding = {
    <entity:Identifier> "(" <arguments:ExpressionList> ")"
        "as" <binding:Identifier> "else" <failure:OutcomeExpression>
        => EntityBinding {
            selection_index: None,
            entity,
            arguments,
            binding,
            failure,
            restriction_failure: None,
            cascade_failure: None,
        },
};

DeleteEntityBinding: EntityBinding = {
    <entity:Identifier> "(" <arguments:ExpressionList> ")"
        "as" <binding:Identifier> "else" <failure:OutcomeExpression>
        <restriction_failure:("restrict" <OutcomeExpression>)?>
        <cascade_failure:("cascade" <OutcomeExpression>)?>
        => EntityBinding {
            selection_index: None,
            entity,
            arguments,
            binding,
            failure,
            restriction_failure,
            cascade_failure,
        },
};

Requirement: Spanned<Requirement> = {
    <lo:@L> "require" <name:Identifier> ":" <condition:Expression>
        "else" <rejection:OutcomeExpression> <hi:@R>
        => parser::spanned(Requirement {
            name,
            condition: condition.into_syntax(),
            rejection,
        }, lo, hi),
};

Effect: Spanned<Effect> = {
    <lo:@L> "set" <target:Path> "=" <value:Expression> <checked_by:("checked_by" <Identifier>)?> <reveals:RevealAnnotation*> <hi:@R>
        => parser::spanned(Effect::Set(SetEffect {
            spelling: WriteSpelling::Set,
            checked_by,
            target,
            value: value.into_syntax(),
            reveals,
        }), lo, hi),
    <lo:@L> "overwrite" <target:Path> "=" <value:Expression> <checked_by:("checked_by" <Identifier>)?> <reveals:RevealAnnotation*> <hi:@R>
        => parser::spanned(Effect::Set(SetEffect {
            spelling: WriteSpelling::Overwrite,
            checked_by,
            target,
            value: value.into_syntax(),
            reveals,
        }), lo, hi),
    <lo:@L> "embed" <target:Path> "=" <value:Expression>
        "from" "(" <model_identity:Expression> "," <model_version:Expression> ")" <hi:@R>
        => parser::spanned(Effect::Embed(EmbeddingEffect {
            target,
            value: value.into_syntax(),
            model_identity: model_identity.into_syntax(),
            model_version: model_version.into_syntax(),
        }), lo, hi),
    <lo:@L> "emit" <event:Identifier> <payload:ObjectLiteral> <hi:@R>
        => parser::spanned(Effect::Emit(EmitEffect { event, payload }), lo, hi),
    <lo:@L> "transition" <transition:Identifier> "on" <binding:Identifier>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "illegal" <illegal:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowTransition(WorkflowTransitionEffect {
            transition,
            binding,
            expected_revision: expected_revision.into_syntax(),
            stale,
            illegal,
        }), lo, hi),
    <lo:@L> "lease" "claim" <lease:Identifier> "on" <binding:Identifier>
        "owner" <owner:Expression>
        "duration_seconds" <duration_seconds:Expression>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "unavailable" <unavailable:OutcomeExpression>
        "invalid" <invalid:OutcomeExpression>
        "exhausted" <exhausted:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowLease(Box::new(WorkflowLeaseEffect {
            lease,
            binding,
            operation: WorkflowLeaseOperation::Claim {
                owner: owner.into_syntax(),
                duration_seconds: duration_seconds.into_syntax(),
                expected_revision: expected_revision.into_syntax(),
                stale,
                unavailable,
                invalid,
                exhausted,
            },
        })), lo, hi),
    <lo:@L> "lease" "renew" <lease:Identifier> "on" <binding:Identifier>
        "owner" <owner:Expression>
        "fencing_token" <fencing_token:Expression>
        "duration_seconds" <duration_seconds:Expression>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "invalid" <invalid:OutcomeExpression>
        "expired" <expired:OutcomeExpression>
        "exhausted" <exhausted:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowLease(Box::new(WorkflowLeaseEffect {
            lease,
            binding,
            operation: WorkflowLeaseOperation::Renew {
                owner: owner.into_syntax(),
                fencing_token: fencing_token.into_syntax(),
                duration_seconds: duration_seconds.into_syntax(),
                expected_revision: expected_revision.into_syntax(),
                stale,
                invalid,
                expired,
                exhausted,
            },
        })), lo, hi),
    <lo:@L> "lease" "release" <lease:Identifier> "on" <binding:Identifier>
        "owner" <owner:Expression>
        "fencing_token" <fencing_token:Expression>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "invalid" <invalid:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowLease(Box::new(WorkflowLeaseEffect {
            lease,
            binding,
            operation: WorkflowLeaseOperation::Release {
                owner: owner.into_syntax(),
                fencing_token: fencing_token.into_syntax(),
                expected_revision: expected_revision.into_syntax(),
                stale,
                invalid,
            },
        })), lo, hi),
    <lo:@L> "lease" "expire" <lease:Identifier> "on" <binding:Identifier>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "active" <active:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowLease(Box::new(WorkflowLeaseEffect {
            lease,
            binding,
            operation: WorkflowLeaseOperation::Expire {
                expected_revision: expected_revision.into_syntax(),
                stale,
                active,
            },
        })), lo, hi),
    <lo:@L> "lease" "fence" <lease:Identifier> "on" <binding:Identifier>
        "owner" <owner:Expression>
        "fencing_token" <fencing_token:Expression>
        "revision" <expected_revision:Expression>
        "stale" <stale:OutcomeExpression>
        "invalid" <invalid:OutcomeExpression>
        "expired" <expired:OutcomeExpression> <hi:@R>
        => parser::spanned(Effect::WorkflowLease(Box::new(WorkflowLeaseEffect {
            lease,
            binding,
            operation: WorkflowLeaseOperation::Fence {
                owner: owner.into_syntax(),
                fencing_token: fencing_token.into_syntax(),
                expected_revision: expected_revision.into_syntax(),
                stale,
                invalid,
                expired,
            },
        })), lo, hi),
};

ReturnClause: Spanned<ReturnClause> = {
    <lo:@L> "return" <outcome:OutcomeExpression> <hi:@R>
        => parser::spanned(ReturnClause { outcome }, lo, hi),
};

OutcomeExpression: Spanned<OutcomeExpression> = {
    <lo:@L> <name:Identifier> <payload:ObjectLiteral> <hi:@R>
        => parser::spanned(OutcomeExpression { name, payload }, lo, hi),
};

ObjectLiteral: Spanned<ObjectLiteral> = {
    <lo:@L> "{" <fields:ObjectFieldList?> "}" <hi:@R>
        => parser::spanned(ObjectLiteral { fields: fields.unwrap_or_default() }, lo, hi),
};

ObjectFieldList: Vec<Spanned<ObjectField>> = {
    <head:ObjectField> <tail:("," <ObjectField>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head];
        values.extend(tail);
        values
    },
};

ObjectField: Spanned<ObjectField> = {
    <lo:@L> <name:Identifier> ":" <value:Expression> <reveals:RevealAnnotation*> <hi:@R>
        => parser::spanned(ObjectField { name, value: value.into_syntax(), reveals }, lo, hi),
};

RevealAnnotation: Spanned<Path> = {
    "reveals" <source:Path> => source,
};

ProjectionDeclaration: ProjectionDeclaration = {
    "projection" <name:Identifier> "{"
        "source" "event" <source_event:Identifier>
        <filter:WhereClause?>
        "key" "(" <key:ProjectionKeyList> ")"
        <measures:Measure+>
        <frontier:FrontierClause>
    "}" => ProjectionDeclaration {
        name,
        source_event,
        filter,
        key,
        measures,
        frontier,
    },
};

ColumnarProjectionDeclaration: ColumnarProjectionDeclaration = {
    "columnar_projection" <name:Identifier> "{"
        "source" "entity" <source_entity:Identifier>
        <fields_keyword:Identifier> "(" <fields:IdentifierList> ")"
    "}" =>? parser::grammar_result(parser::columnar_projection_declaration(
        name,
        source_entity,
        fields_keyword,
        fields,
    )),
};

ProjectionKeyList: Vec<Spanned<Expression>> = {
    <head:ProjectionKeyExpression> <tail:("," <ProjectionKeyExpression>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head.into_syntax()];
        values.extend(tail.into_iter().map(ParsedExpression::into_syntax));
        values
    },
};

ProjectionKeyExpression: ParsedExpression = {
    <lo:@L> <callee:Identifier> "(" <operand:Expression> ")" <hi:@R>
        =>? parser::grammar_result(parser::temporal_bucket_expression(callee, operand, parser::span(lo, hi))),
    Expression,
};

WhereClause: Spanned<Expression> = {
    "where" <expression:Expression> => expression.into_syntax(),
};

Measure: Spanned<Measure> = {
    <lo:@L> "measure" <name:Identifier> "=" <aggregation:Aggregation> <hi:@R>
        => parser::spanned(Measure { name, aggregation }, lo, hi),
};

Aggregation: Spanned<Aggregation> = {
    <lo:@L> "count" "(" ")" <hi:@R>
        => parser::spanned(Aggregation::Count, lo, hi),
    <lo:@L> "sum" "(" <expression:Expression> ")" <hi:@R>
        => parser::spanned(Aggregation::Sum(expression.into_syntax()), lo, hi),
    <lo:@L> <callee:Identifier> "(" <expression:Expression> ")" <hi:@R>
        =>? parser::grammar_result(parser::named_aggregation(callee, expression, lo, hi)),
};

FrontierClause: Spanned<Frontier> = {
    <lo:@L> "frontier" "transactionally_ordered" <hi:@R>
        => parser::spanned(Frontier::TransactionallyOrdered, lo, hi),
};

ExpressionList: Vec<Spanned<Expression>> = {
    <head:Expression> <tail:("," <Expression>)*> <trailing:Comma?> => {
        let _ = trailing;
        let mut values = vec![head.into_syntax()];
        values.extend(tail.into_iter().map(ParsedExpression::into_syntax));
        values
    },
};

Expression: ParsedExpression = OrExpression;

OrExpression: ParsedExpression = {
    <left:AndExpression> <rest:(<OrOperator> <AndExpression>)*>
        =>? parser::grammar_result(parser::binary_chain(left, rest)),
};

AndExpression: ParsedExpression = {
    <left:ComparisonExpression> <rest:(<AndOperator> <ComparisonExpression>)*>
        =>? parser::grammar_result(parser::binary_chain(left, rest)),
};

ComparisonExpression: ParsedExpression = {
    <left:AdditiveExpression> <rest:(<ComparisonOperator> <AdditiveExpression>)*>
        =>? parser::grammar_result(parser::binary_chain(left, rest)),
};

AdditiveExpression: ParsedExpression = {
    <left:MultiplicativeExpression> <rest:(<AdditiveOperator> <MultiplicativeExpression>)*>
        =>? parser::grammar_result(parser::binary_chain(left, rest)),
};

MultiplicativeExpression: ParsedExpression = {
    <left:UnaryExpression> <rest:(<MultiplicativeOperator> <UnaryExpression>)*>
        =>? parser::grammar_result(parser::binary_chain(left, rest)),
};

UnaryExpression: ParsedExpression = {
    <lo:@L> <operator:UnaryOperator> <operand:UnaryExpression> <hi:@R>
        =>? parser::grammar_result(parser::unary_expression(operator, operand, parser::span(lo, hi))),
    PrimaryExpression,
};

PrimaryExpression: ParsedExpression = {
    <literal:Literal> => parser::literal_expression(literal),
    <path:Path> => parser::path_expression(path),
    <lo:@L> "(" <expression:Expression> ")" <hi:@R>
        =>? parser::grammar_result(parser::parenthesized_expression(expression, parser::span(lo, hi))),
};

Literal: Spanned<Literal> = {
    <lo:@L> "true" <hi:@R> => parser::spanned(Literal::Bool(true), lo, hi),
    <lo:@L> "false" <hi:@R> => parser::spanned(Literal::Bool(false), lo, hi),
    <lo:@L> "null" <hi:@R> => parser::spanned(Literal::Null, lo, hi),
    <lo:@L> <value:"unsigned integer literal"> <hi:@R>
        => parser::spanned(Literal::UInt(value), lo, hi),
    <lo:@L> <value:"fixed decimal literal"> <hi:@R>
        => parser::spanned(Literal::FixedDecimal(value), lo, hi),
    <lo:@L> <value:"string literal"> <hi:@R>
        => parser::spanned(Literal::String(value), lo, hi),
};

Path: Spanned<Path> = {
    <lo:@L> <head:Identifier> <tail:("." <PathSegment>)*> <hi:@R> => {
        let mut segments = vec![head];
        segments.extend(tail);
        parser::spanned(Path { segments }, lo, hi)
    },
};

PathSegment: Spanned<String> = {
    Identifier,
    <lo:@L> "date" <hi:@R> => parser::spanned("date".to_owned(), lo, hi),
};

UnaryOperator: Spanned<UnaryOperator> = {
    <lo:@L> "!" <hi:@R> => parser::spanned(UnaryOperator::Not, lo, hi),
    <lo:@L> "-" <hi:@R> => parser::spanned(UnaryOperator::Negate, lo, hi),
};

MultiplicativeOperator: Spanned<BinaryOperator> = {
    <lo:@L> "*" <hi:@R> => parser::spanned(BinaryOperator::Multiply, lo, hi),
    <lo:@L> "/" <hi:@R> => parser::spanned(BinaryOperator::Divide, lo, hi),
};

AdditiveOperator: Spanned<BinaryOperator> = {
    <lo:@L> "+" <hi:@R> => parser::spanned(BinaryOperator::Add, lo, hi),
    <lo:@L> "-" <hi:@R> => parser::spanned(BinaryOperator::Subtract, lo, hi),
};

ComparisonOperator: Spanned<BinaryOperator> = {
    <lo:@L> "==" <hi:@R> => parser::spanned(BinaryOperator::Equal, lo, hi),
    <lo:@L> "!=" <hi:@R> => parser::spanned(BinaryOperator::NotEqual, lo, hi),
    <lo:@L> "<" <hi:@R> => parser::spanned(BinaryOperator::Less, lo, hi),
    <lo:@L> "<=" <hi:@R> => parser::spanned(BinaryOperator::LessEqual, lo, hi),
    <lo:@L> ">" <hi:@R> => parser::spanned(BinaryOperator::Greater, lo, hi),
    <lo:@L> ">=" <hi:@R> => parser::spanned(BinaryOperator::GreaterEqual, lo, hi),
};

AndOperator: Spanned<BinaryOperator> = {
    <lo:@L> "&&" <hi:@R> => parser::spanned(BinaryOperator::And, lo, hi),
};

OrOperator: Spanned<BinaryOperator> = {
    <lo:@L> "||" <hi:@R> => parser::spanned(BinaryOperator::Or, lo, hi),
};

Identifier: Spanned<String> = {
    <lo:@L> <value:"identifier"> <hi:@R> => parser::spanned(value, lo, hi),
    <lo:@L> "cover" <hi:@R> => parser::spanned("cover".to_owned(), lo, hi),
    <lo:@L> "presence" <hi:@R> => parser::spanned("presence".to_owned(), lo, hi),
    <lo:@L> "text_key" <hi:@R> => parser::spanned("text_key".to_owned(), lo, hi),
    <lo:@L> "binary_utf8_v1" <hi:@R>
        => parser::spanned("binary_utf8_v1".to_owned(), lo, hi),
    <lo:@L> "unicode_fold_v1" <hi:@R>
        => parser::spanned("unicode_fold_v1".to_owned(), lo, hi),
    <lo:@L> "cascade" <hi:@R> => parser::spanned("cascade".to_owned(), lo, hi),
    <lo:@L> "relationship" <hi:@R>
        => parser::spanned("relationship".to_owned(), lo, hi),
    <lo:@L> "using" <hi:@R> => parser::spanned("using".to_owned(), lo, hi),
    <lo:@L> "maximum" <hi:@R> => parser::spanned("maximum".to_owned(), lo, hi),
    <lo:@L> "idempotency_key" <hi:@R>
        => parser::spanned("idempotency_key".to_owned(), lo, hi),
    <lo:@L> "workflow" <hi:@R> => parser::spanned("workflow".to_owned(), lo, hi),
    <lo:@L> "state" <hi:@R> => parser::spanned("state".to_owned(), lo, hi),
    <lo:@L> "initial" <hi:@R> => parser::spanned("initial".to_owned(), lo, hi),
    <lo:@L> "transition" <hi:@R> => parser::spanned("transition".to_owned(), lo, hi),
    <lo:@L> "from" <hi:@R> => parser::spanned("from".to_owned(), lo, hi),
    <lo:@L> "to" <hi:@R> => parser::spanned("to".to_owned(), lo, hi),
    <lo:@L> "lease" <hi:@R> => parser::spanned("lease".to_owned(), lo, hi),
    <lo:@L> "claim" <hi:@R> => parser::spanned("claim".to_owned(), lo, hi),
    <lo:@L> "renew" <hi:@R> => parser::spanned("renew".to_owned(), lo, hi),
    <lo:@L> "release" <hi:@R> => parser::spanned("release".to_owned(), lo, hi),
    <lo:@L> "expire" <hi:@R> => parser::spanned("expire".to_owned(), lo, hi),
    <lo:@L> "fence" <hi:@R> => parser::spanned("fence".to_owned(), lo, hi),
    <lo:@L> "owner" <hi:@R> => parser::spanned("owner".to_owned(), lo, hi),
    <lo:@L> "expires_at" <hi:@R> => parser::spanned("expires_at".to_owned(), lo, hi),
    <lo:@L> "fencing_token" <hi:@R> => parser::spanned("fencing_token".to_owned(), lo, hi),
    <lo:@L> "attempts" <hi:@R> => parser::spanned("attempts".to_owned(), lo, hi),
    <lo:@L> "duration_seconds" <hi:@R>
        => parser::spanned("duration_seconds".to_owned(), lo, hi),
    <lo:@L> "service" <hi:@R> => parser::spanned("service".to_owned(), lo, hi),
    <lo:@L> "uuid_v7" <hi:@R> => parser::spanned("uuid_v7".to_owned(), lo, hi),
    <lo:@L> "transaction_time" <hi:@R>
        => parser::spanned("transaction_time".to_owned(), lo, hi),
    <lo:@L> "on" <hi:@R> => parser::spanned("on".to_owned(), lo, hi),
    <lo:@L> "revision" <hi:@R> => parser::spanned("revision".to_owned(), lo, hi),
    <lo:@L> "stale" <hi:@R> => parser::spanned("stale".to_owned(), lo, hi),
    <lo:@L> "illegal" <hi:@R> => parser::spanned("illegal".to_owned(), lo, hi),
    <lo:@L> "unavailable" <hi:@R> => parser::spanned("unavailable".to_owned(), lo, hi),
    <lo:@L> "invalid" <hi:@R> => parser::spanned("invalid".to_owned(), lo, hi),
    <lo:@L> "exhausted" <hi:@R> => parser::spanned("exhausted".to_owned(), lo, hi),
    <lo:@L> "expired" <hi:@R> => parser::spanned("expired".to_owned(), lo, hi),
    <lo:@L> "active" <hi:@R> => parser::spanned("active".to_owned(), lo, hi),
    <lo:@L> "principal" <hi:@R> => parser::spanned("principal".to_owned(), lo, hi),
    <lo:@L> "fact" <hi:@R> => parser::spanned("fact".to_owned(), lo, hi),
    <lo:@L> "row" <hi:@R> => parser::spanned("row".to_owned(), lo, hi),
    <lo:@L> "policy" <hi:@R> => parser::spanned("policy".to_owned(), lo, hi),
    <lo:@L> "allow" <hi:@R> => parser::spanned("allow".to_owned(), lo, hi),
    <lo:@L> "when" <hi:@R> => parser::spanned("when".to_owned(), lo, hi),
    <lo:@L> "update" <hi:@R> => parser::spanned("update".to_owned(), lo, hi),
    <lo:@L> "delete" <hi:@R> => parser::spanned("delete".to_owned(), lo, hi),
    <lo:@L> "in" <hi:@R> => parser::spanned("in".to_owned(), lo, hi),
    <lo:@L> "is" <hi:@R> => parser::spanned("is".to_owned(), lo, hi),
    <lo:@L> "not" <hi:@R> => parser::spanned("not".to_owned(), lo, hi),
    <lo:@L> "reveals" <hi:@R> => parser::spanned("reveals".to_owned(), lo, hi),
};

UnsignedInteger: Spanned<String> = {
    <lo:@L> <value:"unsigned integer literal"> <hi:@R>
        => parser::spanned(value, lo, hi),
};

StringLiteral: Spanned<String> = {
    <lo:@L> <value:"string literal"> <hi:@R>
        => parser::spanned(value, lo, hi),
};

Comma: () = { "," => () };

extern {
    type Location = usize;
    type Error = SyntaxDiagnostic;

    enum Token {
        "contract" => Token::Contract,
        "version" => Token::Version,
        "entity" => Token::Entity,
        "key" => Token::Key,
        "field" => Token::Field,
        "invariant" => Token::Invariant,
        "index" => Token::Index,
        "cover" => Token::Cover,
        "presence" => Token::Presence,
        "text_key" => Token::TextKey,
        "binary_utf8_v1" => Token::BinaryUtf8V1,
        "unicode_fold_v1" => Token::UnicodeFoldV1,
        "unique" => Token::Unique,
        "reference" => Token::Reference,
        "vector_field" => Token::VectorField,
        "text_index" => Token::TextIndex,
        "pattern_index" => Token::LongPattern,
        "delete_policy" => Token::DeletePolicy,
        "no_inbound" => Token::NoInbound,
        "restrict" => Token::Restrict,
        "cascade" => Token::Cascade,
        "relationship" => Token::Relationship,
        "using" => Token::Using,
        "maximum" => Token::Maximum,
        "event" => Token::Event,
        "enum" => Token::Enum,
        "aggregate" => Token::Aggregate,
        "root" => Token::Root,
        "child" => Token::Child,
        "partition_by" => Token::PartitionBy,
        "policy_anchor" => Token::PolicyAnchor,
        "conflict_key" => Token::ConflictKey,
        "projection" => Token::Projection,
        "columnar_projection" => Token::ColumnarProjection,
        "source" => Token::Source,
        "where" => Token::Where,
        "measure" => Token::Measure,
        "count" => Token::Count,
        "sum" => Token::Sum,
        "frontier" => Token::Frontier,
        "transactionally_ordered" => Token::TransactionallyOrdered,
        "command" => Token::Command,
        "bulk" => Token::Bulk,
        "reimport" => Token::Reimport,
        "reconstitute" => Token::Reconstitute,
        "for" => Token::For,
        "workflow" => Token::Workflow,
        "state" => Token::State,
        "initial" => Token::Initial,
        "transition" => Token::Transition,
        "from" => Token::From,
        "to" => Token::To,
        "lease" => Token::Lease,
        "claim" => Token::Claim,
        "renew" => Token::Renew,
        "release" => Token::Release,
        "expire" => Token::Expire,
        "fence" => Token::Fence,
        "owner" => Token::Owner,
        "expires_at" => Token::ExpiresAt,
        "fencing_token" => Token::FencingToken,
        "attempts" => Token::Attempts,
        "duration_seconds" => Token::DurationSeconds,
        "service" => Token::Service,
        "uuid_v7" => Token::UuidV7,
        "transaction_time" => Token::TransactionTime,
        "on" => Token::On,
        "revision" => Token::Revision,
        "stale" => Token::Stale,
        "illegal" => Token::Illegal,
        "unavailable" => Token::Unavailable,
        "invalid" => Token::Invalid,
        "exhausted" => Token::Exhausted,
        "expired" => Token::Expired,
        "active" => Token::Active,
        "input" => Token::Input,
        "aggregate_bytes" => Token::AggregateBytes,
        "idempotency_key" => Token::IdempotencyKey,
        "read" => Token::Read,
        "mutate" => Token::Mutate,
        "create" => Token::Create,
        "init_or_mutate" => Token::InitOrMutate,
        "observe_or_initialize" => Token::ObserveOrInitialize,
        "decide" => Token::Decide,
        "apply" => Token::Apply,
        "no_effect" => Token::NoEffect,
        "reject" => Token::Reject,
        "initialize" => Token::Initialize,
        "as" => Token::As,
        "else" => Token::Else,
        "require" => Token::Require,
        "set" => Token::Set,
        "overwrite" => Token::Overwrite,
        "checked_by" => Token::CheckedBy,
        "embed" => Token::Embed,
        "emit" => Token::Emit,
        "return" => Token::Return,
        "reveals" => Token::Reveals,
        "principal" => Token::Principal,
        "fact" => Token::Fact,
        "row" => Token::Row,
        "policy" => Token::Policy,
        "allow" => Token::Allow,
        "when" => Token::When,
        "update" => Token::Update,
        "delete" => Token::Delete,
        "in" => Token::In,
        "is" => Token::Is,
        "not" => Token::Not,
        "exists" => Token::Exists,
        "bool" => Token::Bool,
        "i64" => Token::I64,
        "u64" => Token::U64,
        "timestamp" => Token::Timestamp,
        "date" => Token::Date,
        "uuid" => Token::Uuid,
        "decimal" => Token::Decimal,
        "money" => Token::Money,
        "string" => Token::String,
        "bytes" => Token::Bytes,
        "vector" => Token::Vector,
        "optional" => Token::Optional,
        "list" => Token::List,
        "true" => Token::True,
        "false" => Token::False,
        "null" => Token::Null,
        "{" => Token::LeftBrace,
        "}" => Token::RightBrace,
        "(" => Token::LeftParen,
        ")" => Token::RightParen,
        "<=" => Token::LessEqual,
        ">=" => Token::GreaterEqual,
        "->" => Token::Arrow,
        "=>" => Token::FatArrow,
        "==" => Token::EqualEqual,
        "!=" => Token::BangEqual,
        "&&" => Token::AndAnd,
        "||" => Token::OrOr,
        "<" => Token::Less,
        ">" => Token::Greater,
        "," => Token::Comma,
        ":" => Token::Colon,
        "." => Token::Dot,
        "=" => Token::Equal,
        "!" => Token::Bang,
        "-" => Token::Minus,
        "*" => Token::Star,
        "/" => Token::Slash,
        "+" => Token::Plus,
        "fixed decimal literal" => Token::FixedDecimalLiteral(<String>),
        "unsigned integer literal" => Token::UIntLiteral(<String>),
        "unsigned integer range" => Token::UIntRange(<(String, String)>),
        "string literal" => Token::StringLiteral(<String>),
        "identifier" => Token::Identifier(<String>),
    }
}