//! The Nickel grammar.
//!
//! # Uniterm
//!
//! Nickel uses the uniterm grammar since
//! [RFC002](../rfcs/002-merge-types-terms-syntax.md). Uniterm is a common
//! grammar for both term and types. However, it is only a front-end: the rest of
//! the interpreter pipeline needs terms and types to be separate objects.
//!
//! Most of the time, grammar constructs determine unambiguously if an expression
//! should be considered as a type or a term. Typically, `e1 -> e2` will always
//! be a type, and `e1 + e2` a term. This doesn't contradict the fact that `e1 ->
//! e2` can be used as a term: the point is, even in the latter case, we still
//! parse `e1 -> e2` as a type first, and then derive a term from it wherever it
//! is used in a context expecting a term.
//!
//! This is not the case of all rules. Record literals and variables can both be
//! interpreted in a different way, depending on how their usage. In
//! `x : {foo : Num}`, `{foo : Num}` is interpreted as a record type. In `{foo :
//! Num}.foo`, it is a record literal with a missing definition for `foo` (note:
//! this latter form is now forbidden in the syntax). The first interpretation
//! is **not** equivalent to first interpreting it as a term, and then as a
//! type.
//!
//! For those reasons, the `uniterm` module introduces a new AST definition, that
//! just wraps `Ast` and `Type`, together with dedicated variants for the
//! common constructs that are variables and records. As long as a common
//! construct is not used in a term or a type context, it can be still
//! interpreted as both. Once the usage determines the nature of a record or a
//! variable, it is converted to either a `Ast` or a `Type` (although still
//! possibly wrapped as a `UniTerm`).
//!
//! In consequence, this grammar uses three main types `Ast`, `Type` and
//! `UniTerm`, as well as conversion macros `AsTerm`, `AsType` and `AsUniTerm`.
//!
//! Rules that are known to only produce `Ast` or `Type` may have the
//! corresponding more precise return type. Other rules that produce or just
//! propagate general uniterms have to return a `UniTerm`.
use std::{
ffi::OsString,
convert::TryFrom,
iter,
collections::HashSet,
};
use lalrpop_util::ErrorRecovery;
use super::{
ExtendedTerm,
utils::*,
lexer::{Token, NormalToken, StringToken, MultiStringToken, SymbolicStringStart},
error::{ParseError, ParseOrLexError},
uniterm::*,
};
use crate::{
files::FileId,
identifier::{Ident, LocIdent},
combine::CombineAlloc,
ast::{
Ast, Node, Annotation, LetMetadata, AstAlloc, LetBinding, MatchBranch,
Number,
builder, StringChunk, MergeKind, MergePriority, TryConvert,
RecordOpKind,
record::{FieldMetadata, FieldDef, FieldPathElem, Include},
pattern::*,
typ::*,
primop::PrimOp,
},
typ::{VarKind, DictTypeFlavour},
position::{TermPos, RawSpan},
app,
primop_app,
fun,
};
use malachite::base::num::basic::traits::Zero;
grammar<'input, 'ast, 'err, 'wcard>(
alloc: &'ast AstAlloc,
src_id: FileId,
errors: &'err mut Vec<ErrorRecovery<usize, Token<'input>, ParseOrLexError>>,
next_wildcard_id: &'wcard mut usize,
);
// Takes a rule producing a `Node` and automatically attach a position to make it
// an `Ast`.
Spanned<Rule>: Ast<'ast> = <left: @L> <node: Rule> <right: @R> =>
node.spanned(mk_pos(src_id, left, right));
// Takes a rule producing a `Node` and automatically attach a position to make it
// an `Ast`.
SpannedId<Rule>: LocIdent = <left: @L> <id: Rule> <right: @R> =>
id.spanned(mk_pos(src_id, left, right));
// Takes a rule producing a `Node` and automatically attach a position to make it
// an `Ast`.
SpannedTy<Rule>: Type<'ast> = <left: @L> <ty: Rule> <right: @R> =>
ty.spanned(mk_pos(src_id, left, right));
// Takes a rule producing a spanned value with a `with_pos` method (can be an
// `Ast`, but not only) and re-assigns the position to the span of the rule.
WithPos<Rule>: Rule = <left: @L> <t: Rule> <right: @R> =>
t.with_pos(mk_pos(src_id, left, right));
AsTerm<Rule>: Ast<'ast> = <ut: WithPos<Rule>> =>?
Ast::try_convert(alloc, ut)
.map_err(lalrpop_util::ParseError::from);
AsType<Rule>: Type<'ast> = <ut: WithPos<Rule>> =>?
Type::try_convert(alloc, ut)
.map_err(lalrpop_util::ParseError::from);
// Repeat a rule zero times or more with a separator interspersed, such that the last
// separator is optional: for example, RepeatSep<Term, ","> will both accept
// `1,2` and `1,2,`.
RepeatSep<Rule, Sep>: Vec<Rule> = <mut elems: (<Rule> Sep)*> <last: Rule?> => {
elems.extend(last);
elems
};
// Same as `RepeatSep`, but repeat the rule at least once (one or more), instead
// of zero or more.
RepeatSep1<Rule, Sep>: Vec<Rule> = <mut elems: (<Rule> Sep)*> <last: Rule> Sep? => {
elems.push(last);
elems
};
AsUniTerm<Rule>: UniTerm<'ast> = <ut: WithPos<Rule>> => UniTerm::from(ut);
// Macro repeating a rule producing some form of annotation (that can be
// repeated and combined, typically field metadata).
AnnotSeries<AnnotAtom>: AnnotAtom = <AnnotAtom+> => {
<>
.into_iter()
.fold(Default::default(), |acc, next| CombineAlloc::combine(alloc, acc, next))
};
// A single type or contract annotation. The `Type` rule forbids the use of
// constructs that can themselves have annotation on the right, such as a `let`.
// Otherwise, `foo | let x = 1 in x : Number` is ambiguous (the annotation could
// be either `foo | (let x = 1 in (x : Number))` or `(foo | let x = 1 in x) :
// Number`).
//
// The rule to use for type annotations is given as a parameter. We always use a
// rule that is syntactically equivalent to the `Type` rule. The parameter is
// here to control if the type should have its variables fixed now (`FixedType`)
// or later (bare `Type`). Almost all rules are of the former kind, and use
// `FixedType` (see `FixedType` and `parser::utils::fix_type_vars`).
AnnotAtom<TypeRule>: Annotation<'ast> = {
"|" <TypeRule> => Annotation {
contracts: alloc.alloc_singleton(<>),
..Default::default()
},
":" <TypeRule> => Annotation {
typ: Some(<>),
..Default::default()
},
};
// A single metadata annotation attached to a let-binding. Compared to
// annotations which can appear everywhere (`AnnotAtom`, either a type or a
// contract annotation), let annotations also include documentation (`doc`). As
// opposed to record fields, they can't express priority, optionality, etc.
LetAnnotAtom<TypeRule>: LetMetadata<'ast> = {
AnnotAtom<TypeRule> => LetMetadata {
annotation: <>,
..Default::default()
},
"|" "doc" <StaticString> => LetMetadata {
doc: Some(alloc.alloc_str(&<>)),
..Default::default()
},
}
// A single field metadata annotation. The rule to use for type annotations is
// given as a parameter (cf AnnotAtom rule).
FieldAnnotAtom<TypeRule>: FieldMetadata<'ast> = {
<LetAnnotAtom<TypeRule>> => <>.into(),
"|" "default" => FieldMetadata {
priority: MergePriority::Bottom,
..Default::default()
},
"|" "force" => FieldMetadata {
priority: MergePriority::Top,
..Default::default()
},
"|" "priority" <SignedNumLiteral> => FieldMetadata {
priority: MergePriority::Numeral(<>),
..Default::default()
},
"|" "optional" => FieldMetadata {
opt: true,
..Default::default()
},
"|" "not_exported" => FieldMetadata {
not_exported: true,
..Default::default()
},
}
// Recursive priorities are disabled as of 1.2.0. Their semantics is non trivial
// to adapt to RFC005 that landed in 1.0.0, so they are currently on hold. If we
// drop them altogether, we'll have to clean the corresponding code floating
// around (not only in the parser, but in the internals module, etc.)
//
// The current `FieldAnnot` was named `SimpleFieldAnnot` before commenting this
// part out. If we restore recursive priorities, we might probably revert to the
// old naming.
//
// // A single field metadata annotation.
//
// // The rule to use for type annotations is given as a parameter (cf AnnotAtom
// // rule).
//FieldAnnotAtom<TypeRule>: FieldExtAnnot = {
// <SimpleFieldAnnotAtom<TypeRule>> => <>.into(),
// "|" "rec" "force" => FieldExtAnnot {
// rec_force: true,
// ..Default::default()
// },
// "|" "rec" "default" => FieldExtAnnot {
// rec_default: true,
// ..Default::default()
// },
//}
// An annotation, with possibly many annotations chained.
Annot<TypeRule>: Annotation<'ast> = AnnotSeries<AnnotAtom<WithPos<TypeRule>>>;
// A let annotation, with possibly many annotations chained. Include type
// annotations, contract annotations and doc annotations.
LetAnnot<TypeRule>: LetMetadata<'ast> = AnnotSeries<LetAnnotAtom<WithPos<TypeRule>>>;
// A field annotation, with possibly many annotations chained.
FieldAnnot<TypeRule>: FieldMetadata<'ast> =
AnnotSeries<FieldAnnotAtom<WithPos<TypeRule>>>;
// A general expression. Wrap the root of the grammar as an `Ast`.
pub Term: Ast<'ast> = AsTerm<UniTerm>;
// A general type. Chosen such that it can't have top-level annotations.
// (see `AnnotAtom`)
Type: Type<'ast> = {
AsType<InfixExpr>,
SpannedTy<Forall>,
};
// A type with type variables fixed. See `parser::utils::fix_type_vars`.
//
// This rule is public and can be used from external modules to parse an input
// directly as a type.
pub FixedType: Type<'ast> = {
<l: @L> <ty: Type> <r: @R> =>? {
Ok(ty.fix_type_vars(alloc, mk_span(src_id, l, r))?)
}
};
// Either an expression or a top-level let-binding (a let-binding without an
// `in`). Used exclusively for the REPL.
pub ExtendedTerm: ExtendedTerm<Ast<'ast>> = {
"let" <id: Ident> <ann: LetAnnot<FixedType>?> "=" <mut exp: Term> => {
if let Some(ann) = ann {
exp = ann.annotation.attach_to_ast(alloc, exp);
}
ExtendedTerm::ToplevelLet(id, exp)
},
Term => ExtendedTerm::Term(<>),
};
LetBinding: LetBinding<'ast> = {
<pattern: Pattern> <metadata: LetAnnot<FixedType>?> "=" <value: Term> => {
LetBinding { pattern, metadata: metadata.unwrap_or_default(), value }
}
}
// A general uniterm. The root of the grammar.
UniTerm: UniTerm<'ast> = {
InfixExpr,
AnnotatedInfixExpr,
AsUniTerm<SpannedTy<Forall>>,
"let"
<recursive: "rec"?>
<bindings: RepeatSep1<LetBinding, ",">>
"in" <body: Term> =>? {
Ok(UniTerm::from(mk_let(
alloc,
recursive.is_some(),
bindings,
body,
)?))
},
"fun" <pats: PatternFun+> "=>" <body: Term> => {
UniTerm::from(alloc.fun(pats, body))
},
"if" <cond: Term> "then" <e1: Term> "else" <e2: Term> =>
UniTerm::from(alloc.if_then_else(cond, e1, e2)),
<err: Error> => UniTerm::from(err),
"import" <l: @L> <s: StandardStaticString> <r: @R> =>? {
Ok(UniTerm::from(mk_import_based_on_filename(alloc, s, mk_span(src_id, l, r))?))
},
"import" <s: StandardStaticString> "as" <l: @L> <t: EnumTag> <r: @R> =>? {
Ok(UniTerm::from(mk_import_explicit(alloc, s, t, mk_span(src_id, l, r))?))
},
"import" <pkg: Ident> => {
UniTerm::from(alloc.import_package(pkg.ident()))
}
};
AnnotatedInfixExpr: UniTerm<'ast> = {
<e: AsTerm<InfixExpr>> <ann: Annot<FixedType>> => {
UniTerm::from(ann.attach_to_ast(alloc, e))
},
};
Forall: TypeUnr<'ast> =
"forall" <ids: Ident+> "." <ty: Type> => {
ids.into_iter().rev().fold(
ty,
// The variable kind will be determined during the `fix_type_vars`
// phase. For now, we put an arbitrary one (which is also the
// default one for unused type variables)
|acc, var| {
let pos = acc.pos;
Type {
typ: TypeF::Forall {
var,
var_kind: VarKind::Type,
body: alloc.alloc(acc),
},
pos
}
}
).typ
};
// The possible heads of function application. The head of a multi-argument
// application is the leftmost part in `<head> <arg1> ... <argn>`.
ApplicativeHead: UniTerm<'ast> = {
Atom,
AsUniTerm<SpannedTy<TypeArray>>,
<op: UOp> <t: AsTerm<Atom>> => UniTerm::from(primop_app!(alloc, op, t)),
<op: BOpPre> <t1: AsTerm<Atom>> <t2: AsTerm<Atom>>
=> UniTerm::from(primop_app!(alloc, op, t1, t2)),
NOpPre<AsTerm<Atom>>,
"match" "{" <branches: RepeatSep<MatchBranch, ",">> "}" => UniTerm::from(alloc.match_expr(branches)),
};
// A n-ary application-like expression (n may be 0, in the sense that this rule
// also includes previous levels).
Applicative: UniTerm<'ast> = {
<head: WithPos<ApplicativeHead>> <mut args: AsTerm<Atom>*> =>? {
// A zero-ary application is just the head.
if args.is_empty() {
Ok(head)
}
else {
// For a general application, we need the head to be a term. We
// don't support general type applications yet - `Array T` is
// special cased as a type constructor.
let head = Ast::try_convert(alloc, head).map_err(lalrpop_util::ParseError::from)?;
// We special case the application of an enum tag here. In principle, an
// enum variant applied to an argument is of different nature than a
// function application. However, for convenience, we made the syntax
// the same. So we now have to detect cases like `'Foo {x=1}` and
// convert that to a proper enum variant.
if let (Node::EnumVariant { tag, arg: None }, 1)
= (&head.node, args.len()) {
Ok(alloc.enum_variant(*tag, args.pop()).into())
}
else {
Ok(alloc.app(head, args).into())
}
}
},
};
// The parametrized array type.
TypeArray: TypeUnr<'ast> = "Array" <t: AsType<Atom>> =>
// For some reason, we have to bind the type into a `t`
// rather than using the usual `<>` placeholder, otherwise,
// it doesn't compile.
TypeF::Array(alloc.alloc(t));
// A record operation chain, such as `{foo = data}.bar.baz`.
RecordOperationChain: Node<'ast> = {
<e: AsTerm<Atom>> "." <id: ExtendedIdent> =>
alloc.prim_op(PrimOp::RecordStatAccess(id), iter::once(e)),
<e: AsTerm<Atom>> "." <t_id: Spanned<StringChunks>> => mk_access(alloc, t_id, e),
};
RecordRowTail: RecordRows<'ast> = {
<Ident> => RecordRows(RecordRowsF::TailVar(<>)),
"Dyn" => RecordRows(RecordRowsF::TailDyn),
};
// A record, that can be later interpreted either as a record literal or as a
// record type.
UniRecord: UniRecord<'ast> = {
"{"
<field_decls: (<FieldDecl> ",")*>
<last_l: @L> <last: LastField?> <last_r: @R>
<tail_l: @L> <tail: (";" RecordRowTail)?> <tail_r: @R>
"}" =>? {
let (last_field, open) = match last {
Some(LastField::FieldDecl(decl)) => (Some(decl), false),
Some(LastField::Ellipsis) => (None, true),
None => (None, false)
};
let pos_ellipsis = if open {
mk_pos(src_id, last_l, last_r)
}
else {
TermPos::None
};
// We expect `includes` to be much less common than field definitions,
// and `includes` to be at most a few fields.
let mut fields = Vec::with_capacity(field_decls.len());
let mut includes = Vec::new();
// For now, we don't accept piecewise definitions involving both an
// include and a field definition. We maintain a set of definitions and
// includes that we've seen and check at the end that the intersection
// is empty.
//
// Note that `LocIdent`'s hash function doesn't take the location into
// account, only the identifier, which is what we want. This means
// retrieving the same `LocIdent` from one set or the other will
// actually return idents with different positions, and this difference
// is meaningful for error reporting.
let mut defs_seen = HashSet::new();
let mut includes_seen : HashSet<LocIdent> = HashSet::new();
let mut handle_include = |include: Include<'ast>| -> Result<(), _> {
if let Some(prev_id) = includes_seen.get(&include.ident) {
return Err(lalrpop_util::ParseError::from(
ParseError::MultipleFieldDecls {
ident: include.ident.ident(),
// unwrap(): we expect positions to be defined for
// freshly parsed ident
include_span: prev_id.pos.unwrap(),
other_span: include.ident.pos.unwrap(),
}
));
}
includes_seen.insert(include.ident);
includes.push(include);
Ok(())
};
for decl in field_decls
.into_iter()
.chain(last_field.into_iter())
{
match decl {
FieldDecl::Def(field_def) => {
defs_seen.extend(field_def.root_as_ident());
fields.push(field_def);
},
FieldDecl::Include(id) => {
handle_include(id)?;
},
FieldDecl::IncludeList(ids) => {
for id in ids {
handle_include(id)?;
}
}
}
}
if let Some(id) = defs_seen.intersection(&includes_seen).next() {
Err(lalrpop_util::ParseError::from(
ParseError::MultipleFieldDecls {
ident: id.ident(),
// unwrap(): we expect the identifier to be in both hash
// sets since it's in the intersection
// unwrap(): we expect positions to be defined for
// freshly parsed ident
include_span: includes_seen.get(id).unwrap().pos.unwrap(),
other_span: defs_seen.get(id).unwrap().pos.unwrap(),
}
))
}
else {
Ok(UniRecord {
fields,
includes,
tail: tail.map(|t| (t.1, mk_pos(src_id, tail_l, tail_r))),
open,
pos: TermPos::None,
pos_ellipsis,
})
}
},
};
NumberLiteral: Number = {
<"dec num literal">,
<"hex num literal">,
<"oct num literal">,
<"bin num literal">,
};
// The list syntax for arbitrary objects T.
List<T>: Vec<T> = "[" <RepeatSep<T, ",">> "]";
Atom: UniTerm<'ast> = {
"(" <AsUniTerm<Spanned<CurriedOp>>> ")",
"(" <UniTerm> ")",
NumberLiteral => UniTerm::from(alloc.number(<>)),
"null" => UniTerm::from(Node::Null),
Bool => UniTerm::from(Node::Bool(<>)),
AsUniTerm<Spanned<StringChunks>>,
Ident => UniTerm::from(UniTermNode::Var(<>)),
WithPos<UniRecord> => UniTerm::from(UniTermNode::Record(<>)),
EnumTag => UniTerm::from(Node::EnumVariant { tag: <>, arg: None }),
List<Term> => UniTerm::from(alloc.array(<>)),
AsUniTerm<SpannedTy<TypeAtom>>,
AsUniTerm<Spanned<RecordOperationChain>>,
};
// An `include` expression in a record literal.
RecordInclude: Include<'ast> =
// Note that we use a fixed type here: a record literal with an include
// expressions will never be interpreted as a record type, so we can fix its
// annotation right away.
"include" <ident: ExtendedIdent> <ann: FieldAnnot<FixedType>?> =>
Include { ident, metadata: ann.unwrap_or_default() };
// Multiple `include` expressions using the list syntax in a record literal.
RecordIncludeList: Vec<Include<'ast>> = "include" <List<ExtendedIdent>> =>
<>
.into_iter()
.map(|ident| Include { ident, metadata: FieldMetadata::default() })
.collect();
// A record field declaration, that can be either a standard field definition or
// an include expression.
FieldDecl: FieldDecl<'ast> = {
FieldDef => FieldDecl::Def(<>),
RecordInclude => FieldDecl::Include(<>),
RecordIncludeList => FieldDecl::IncludeList(<>),
};
// A record field definition. The is the only place where we don't fix the type
// variables inside the annotation right away (note the `Annot<Type>` instead of
// `Annot<Fixed>`).
FieldDef: FieldDef<'ast> = {
<l: @L>
<path: FieldPath>
<ann: FieldAnnot<Type>?>
<value: ("=" <Term>)?>
<r: @R> => {
FieldDef {
path: alloc.alloc_many(path),
metadata: ann.unwrap_or_default(),
value,
pos: TermPos::Original(mk_span(src_id, l, r)),
}
},
<err: Error> => {
FieldDef {
pos: err.pos,
path: alloc.alloc_singleton(FieldPathElem::Expr(err.clone())),
metadata: Default::default(),
value: None,
}
},
};
// An error recovery handler.
Error : Ast<'ast> = <l: @L> <t: !> <r: @R> => {
let pos = mk_pos(src_id, l, r);
errors.push(t.clone());
alloc
.parse_error(crate::error::ParseError::from_lalrpop(
t.error,
src_id)
)
.spanned(pos)
};
LastField: LastField<'ast> = {
<FieldDecl> => LastField::FieldDecl(<>),
".." => LastField::Ellipsis,
};
// A field path syntax in a field definition, as in `{foo."bar bar".baz = "value"}`.
FieldPath: Vec<FieldPathElem<'ast>> = {
<mut elems: (<FieldPathElem> ".")*> <last: FieldPathElem> => {
elems.push(last);
elems
}
};
// A field path which only contains static string literals, that is, without any
// interpolated expression in it.
pub StaticFieldPath: Vec<LocIdent> = <l: @L> <field_path: FieldPath> <r: @R> =>? {
field_path
.into_iter()
.map(|elem| match elem {
FieldPathElem::Ident(ident) => Ok(ident),
// The `FieldPath` rule already detects expressions that are
// actually static identifiers. If we end up here, we have actual
// dynamic interpolation.
FieldPathElem::Expr(expr) => Err(ParseError::InterpolationInStaticPath {
input: String::new(),
path_elem_span: expr.pos
.into_opt()
.unwrap_or_else(|| mk_span(src_id, l, r)),
}.into()),
})
.collect()
};
// This rule is used to parse value assignments on the command line as part of
// the customize mode, such as `foo.bar.enabled=true` in
//
//```
//$ nickel export config.ncl -- foo.bar.enabled=true
//```
//
// The returned span (of the right-hand side of the equal sign) is required for
// the CLI to extract the substring corresponding to the right-hand side, here
// `true`.
//
// It's redundant with the position stored inside the returned `Ast`. But
// this position might theoretically be `None` - we know it can't in practice,
// because the Term rule inserts position information, but the `TermPos` type
// alone can't encode this invariant.
//
// We could just return a `Node` instead of a `Ast`, as position information is
// already stored in the span. But the <Term> rule produces an Ast anyway, so
// it's simpler to just return it instead of artificially deconstructing it.
//
// This rule is currently only used for the CLI and isn't part of the grammar
// for normal Nickel source code.
pub CliFieldAssignment: (Vec<LocIdent>, Ast<'ast>, RawSpan) =
<path: StaticFieldPath> "=" <start: @L> <value: Term> <end: @R>
=> (path, value, mk_span(src_id, start, end));
FieldPathElem: FieldPathElem<'ast> = {
<ExtendedIdent> => FieldPathElem::Ident(<>),
<Spanned<StringChunks>> => FieldPathElem::expr(<>),
};
// A pattern.
//
// The PatternF, and in general several pattern rules ending with a capital `F`,
// are parametrized by other pattern rules. In general, depending on where those
// patterns and subpatterns occur, they need various restrictions to ensure that
// parsing is never ambiguous (at least with respect to the LALR(1)/LR(1)
// capabilities supported by LALRPOP).
//
// The various flavours of pattern rules and their respective motivation are
// detailed below.
//
// # Parentheses
//
// ## Enum variants
//
// Before the introduction of enum variants, functions have been allowed to
// match on several arguments using a sequence of patterns. For example, `fun
// {x} {y} z => x + y + z`. With variants, we've added the following pattern
// form: `'SomeTag argument`. Now, something like `fun 'SomeTag 'SomeArg => ...`
// is ambiguous: are we matching on a single argument that we expect to be
// `('SomeTag 'SomeArg)`, or on two separate arguments that are bare enum tags,
// as in `fun ('SomeTag) ('SomeArg)`?
//
// To avoid ambiguity, we force the top-level argument patterns of a function to
// use a parenthesized version for enum variants. Thus `fun 'Foo 'Bar => ...` is
// always interpreted as `fun ('Foo) ('Bar) => ...`. The other interpretation
// can be written as `fun ('Foo 'Bar) => ...`.
//
// We allow parenthesized enum variant patterns in general patterns as well, not
// only for consistency, but because they also make nested enum variant patterns
// more readable: `'Foo ('Bar 5)` vs `'Foo 'Bar 5`. In fact, we also force
// nested enum patterns to be parenthesized, and forbid the latter, for better
// readability. In practice, this means that the argument pattern of an enum
// variant pattern has the same restriction as a function argument pattern.
//
// ## or-patterns
//
// The same ambiguity (and solution) extends to or-patterns, which are also
// ambiguous when used as function arguments. As we want `or` to remain a valid
// identifier, `fun x or y => ...` could be a function of 3 arguments `x`, `or`
// and `y`, or a function of one argument matching the pattern `(x or y)` (this
// isn't a valid or-pattern because the bound variables are different in each or
// branch, but that's beside the point - checking variables mismatches isn't the
// job of parsing rules).
//
// We thus reuse the exact same idea in order to force or-patterns used at the
// top-level of a function argument to be parenthesized.
//
// ## Or-patterns ambiguities
//
// Parsing or-patterns without ambiguity, while still allowing `or` to remain
// a valid identifier including within patterns (as in `'Foo or`) requires
// slightly more complicated constraints.
//
// The first issue is parentheses: how to parse `x or y or z`? Here we take a
// simple stance: patterns (enum variant patterns and or-patterns) within an
// `or`-branch must be parenthesized. So, this is parsed as a flat or-pattern
// `[x, y, z]`.
//
// The second, harder issue is that enum variant patterns might have a trailing
// `or` identifier. That is, when seeing `'Foo or`, the parser doesn't know if
// it should shift in the hope of seeing another pattern after the `or`, and
// parsing the overall result as an or-pattern `['Foo, <another pattern]`, or
// if it should reduce to the enum variant pattern `'Foo or` and continue.
//
// The general trick to solve those kind of issues is to regroup the "common
// prefix" in one specific rule and disambiguate as late as possible. In this
// case, more concretely:
//
// 1. We split the enum variant pattern rule in two distinct case: the `'<Tag>
// or` form, where the argument is an `Any` pattern with identifier `or`, and
// everything else
// 2. Within an or-pattern branch, in the generic case, we restrict patterns
// so that they can't include the first form (`'<Tag> or`) nor enum tag
// patterns (like `'Foo`). This way, whether in a `('Foo or)` enum variant
// pattern, or in a `'Foo or 'Bar` or-pattern, the `'Foo or` part is
// invariably parsed using the special rule `EnumVariantOrPattern`.
// 3. Then, in the or-pattern branch rule, we assemble `or` branches which are
// either `'<Tag> or`, which is re-interpreted on the fly not as a enum
// variant pattern, but as an enum tag pattern followed by `or`, or the
// restricted generic form of pattern followed by an actual `or`.
//
// There are other minor details, but with enough variations of pattern rules,
// we can ensure there's only one way to parse each and every combination with
// only one look-ahead, thus satisfying the LR(1).
#[inline]
PatternF<EnumRule, OrRule, IdentRule>: Pattern<'ast> = {
<l: @L>
<alias:(<Ident> "@")?>
<data: PatternDataF<EnumRule, OrRule, IdentRule>>
<r: @R> => {
Pattern {
alias,
data,
pos: mk_pos(src_id, l, r),
}
},
};
#[inline]
PatternDataF<EnumRule, OrRule, IdentRule>: PatternData<'ast> = {
RecordPattern => PatternData::Record(alloc.alloc(<>)),
ArrayPattern => PatternData::Array(alloc.alloc(<>)),
ConstantPattern => PatternData::Constant(alloc.alloc(<>)),
EnumRule => PatternData::Enum(alloc.alloc(<>)),
OrRule => PatternData::Or(alloc.alloc(<>)),
IdentRule => PatternData::Any(<>),
"_" => PatternData::Wildcard,
};
// A general pattern, unrestricted.
#[inline]
Pattern: Pattern<'ast> = PatternF<EnumPattern, OrPattern, Ident>;
// A pattern restricted to function arguments, which requires or-patterns and
// enum variant patterns to be parenthesized at the top-level.
#[inline]
PatternFun: Pattern<'ast> = PatternF<EnumPatternParens, OrPatternParens, Ident>;
// A pattern that can be used within a branch of an or-pattern. To avoid a
// shift-reduce conflicts (because we want to allow `or` to remain a valid
// identifier, even inside patterns), this pattern has the following
// restrictions:
//
// 1. Enum tag patterns are forbidden (such as `'Foo or 'Bar`).
// 2. Enum variant patterns shouldn't have the "or" identifier as an argument.
// 3. Or-pattern must be parenthesized when nested in another or-pattern.
// 4. Aliases are forbidden at the top-level. Otherwise, we run into troubles
// with alias chains. Furthermore, the branches of an or-pattern must have
// the same bound variables, so it usually makes more sense to alias the whole
// or-pattern instead of one specific branch.
//
// See the `PatternF` rule for an explanation of why we need those restrictions.
#[inline]
PatternOrBranch: Pattern<'ast> =
<left: @L>
<data: PatternDataF<EnumPatternOrBranch, OrPatternParens, Ident>>
<right: @R> => {
Pattern {
alias: None,
data,
pos: mk_pos(src_id, left, right),
}
};
ConstantPattern: ConstantPattern<'ast> = {
<start: @L> <data: ConstantPatternData> <end: @R> => ConstantPattern {
data,
pos: mk_pos(src_id, start, end)
}
};
ConstantPatternData: ConstantPatternData<'ast> = {
Bool => ConstantPatternData::Bool(<>),
NumberLiteral => ConstantPatternData::Number(alloc.alloc_number(<>)),
// We could accept multiline strings here, but it's unlikely that this will
// result in very readable match expressions. For now we restrict ourselves
// to standard string; we can always extend to multiline later if needed
StandardStaticString => ConstantPatternData::String(alloc.alloc_str(&<>)),
"null" => ConstantPatternData::Null,
};
RecordPattern: RecordPattern<'ast> = {
<start: @L>
"{"
<mut field_pats: (<FieldPattern> ",")*>
<last: LastFieldPat?>
"}"
<end: @R> =>? {
let tail = match last {
Some(LastPattern::Normal(m)) => {
field_pats.push(m);
TailPattern::Empty
},
Some(LastPattern::Ellipsis(Some(captured))) => {
TailPattern::Capture(captured)
}
Some(LastPattern::Ellipsis(None)) => {
TailPattern::Open
}
None => TailPattern::Empty,
};
let pattern = RecordPattern {
patterns: alloc.alloc_many(field_pats),
tail,
pos: mk_pos(src_id, start, end)
};
pattern.check_dup()?;
Ok(pattern)
}
};
ArrayPattern: ArrayPattern<'ast> = {
<start: @L> "[" <mut patterns: (<Pattern> ",")*> <last: LastElemPat?> "]" <end: @R> => {
let tail = match last {
Some(LastPattern::Normal(m)) => {
patterns.push(m);
TailPattern::Empty
},
Some(LastPattern::Ellipsis(Some(captured))) => {
TailPattern::Capture(captured)
}
Some(LastPattern::Ellipsis(None)) => {
TailPattern::Open
}
None => TailPattern::Empty,
};
ArrayPattern {
patterns: alloc.alloc_many(patterns),
tail,
pos: mk_pos(src_id, start, end)
}
},
};
// A pattern for an enum tag (without argument).
EnumTagPattern: EnumPattern<'ast> = <start: @L> <tag: EnumTag> <end: @R> => EnumPattern {
tag,
pattern: None,
pos: mk_pos(src_id, start, end),
};
// A rule which only matches an enum variant pattern of the form `'<Tag> or`.
// Used to disambiguate between an enum variant pattern and an or-pattern.
EnumVariantOrPattern: EnumPattern<'ast> =
<start: @L>
<tag: EnumTag>
<or_arg: SpannedId<IdentOr>>
<end: @R> => {
let pos_or = or_arg.pos;
EnumPattern {
tag,
pattern: Some(Pattern {
data: PatternData::Any(or_arg),
alias: None,
pos: pos_or,
}),
pos: mk_pos(src_id, start, end),
}
};
// An enum variant pattern, excluding the `EnumVariantPatternOr` case: that is,
// this rule doesn't match the case `'<Tag> or`.
EnumVariantNoOrPattern: EnumPattern<'ast> =
<start: @L>
<tag: EnumTag>
<pattern: PatternF<EnumPatternParens, OrPatternParens, SpannedId<RestrictedIdent>>>
<end: @R> => EnumPattern {
tag,
pattern: Some(pattern),
pos: mk_pos(src_id, start, end),
};
// A pattern for an enum variant (with an argument). To avoid ambiguity, we need
// to decompose it into two disjoint rules, one that only match the `'<Tag> or`
// input and everything else.
//
// The idea is that the former case can also serve as the prefix of an
// or-pattern, as in `'Foo or 'Bar`; but as long as we parse this common
// prefix using the same rule and only disambiguate later, there is no
// shift/reduce conflict.
EnumVariantPattern: EnumPattern<'ast> = {
EnumVariantOrPattern,
EnumVariantNoOrPattern,
};
// A twisted version of EnumPattern made specifically for the branch of an
// or-pattern. As we parse `EnumVariantOrPattern` and treat it specifically in
// an `or` branch (`OrPatternBranch`), we need to remove it from the enum
// pattern rule.
EnumPatternOrBranch: EnumPattern<'ast> = {
EnumVariantNoOrPattern,
// Only a top-level un-parenthesized enum variant pattern can be ambiguous.
// If it's parenthesized, we allow the general version including the "or"
// identifier
"(" <EnumVariantPattern> ")",
};
// An unparenthesized enum pattern (including both enum tags and enum
// variants).
EnumPatternUnparens: EnumPattern<'ast> = {
EnumTagPattern,
EnumVariantPattern,
};
// A parenthesized enum pattern, including both tags and variants (note that an
// enum tag alone is never parenthesized: parentheses only applies to enum
// variant patterns).
EnumPatternParens: EnumPattern<'ast> = {
EnumTagPattern,
"(" <EnumVariantPattern> ")",
}
// The unrestricted rule for enum patterns. Allows both enum tags and enum
// variants, and both parenthesized and un-parenthesized enum variants.
EnumPattern: EnumPattern<'ast> = {
EnumTagPattern,
EnumVariantPattern,
"(" <EnumVariantPattern> ")"
};
// An individual element of an or-pattern, plus a trailing "or". This rule is a
// bit artificial, and is essentially here to dispel the shift/reduce conflict
// around `'Foo or`/`'Foo or 'Bar` explained in the description of `PatternF`.
OrPatternBranch: Pattern<'ast> = {
// To avoid various shift-reduce conflicts, the patterns used within an
// `or`-branch have several restrictions. See the `PatternOrBranch` rule.
<PatternOrBranch> "or",
// A variant pattern of the form `'<Tag> or`. The trick is to instead
// consider it as the enum tag pattern `'<Tag>` followed by the `or`
// contextual keyword after-the-fact.
EnumVariantOrPattern => {
let pos = <>.pos;
Pattern {
pos,
alias: None,
data: PatternData::Enum(alloc.alloc(EnumPattern {
tag: <>.tag,
pattern: None,
pos,
})),
}
},
};
// Unparenthesized or-pattern.
OrPatternUnparens: OrPattern<'ast> = {
<start: @L>
<patterns: OrPatternBranch+>
<last: PatternF<EnumPattern, OrPatternParens, Ident>>
<end: @R> => {
// We need to collect in a vector here because the allocator needs an
// exact sized iterator to know beforehand how much memory it needs to
// reserve
let patterns : Vec<_> =
patterns.into_iter().chain(iter::once(last)).collect();
OrPattern {
patterns: alloc.alloc_many(patterns),
pos: mk_pos(src_id, start, end),
}
},
};
// Parenthesized or-pattern.
OrPatternParens: OrPattern<'ast> = {
"(" <OrPatternUnparens> ")",
};
// Unrestricted or-pattern, which can be parenthesized or not.
OrPattern: OrPattern<'ast> = {
OrPatternUnparens,
OrPatternParens,
}
// A binding `ident = <pattern>` inside a record pattern.
FieldPattern: FieldPattern<'ast> = {
<l: @L> <matched_id:Ident> <annot: Annot<FixedType>?> <default: DefaultAnnot?>
"=" <pattern: Pattern> <r: @R> => FieldPattern {
matched_id,
annotation: annot.unwrap_or_default(),
default,
pattern,
pos: mk_pos(src_id, l, r),
},
<l: @L> <matched_id:Ident> <annot: Annot<FixedType>?> <default: DefaultAnnot?> <r: @R> =>
FieldPattern {
matched_id,
annotation: annot.unwrap_or_default(),
default,
pattern: Pattern {
data: PatternData::Any(matched_id),
pos: matched_id.pos,
alias: None,
},
pos: mk_pos(src_id, l, r)
},
};
// Last field pattern of a record pattern.
//
// We need this rule (together with `LastElemPat`) combining both a last field
// or a potential ellipsis because putting the ellipsis in a separate rule AND
// handling the case of zero fields (`{..}`) isn't possible: the fact that the
// ellipsis will need a "," separator before depends on the presence of zero or
// more fields, but a stand-alone ellipsis rule has no way to get this
// information about previous match.
LastFieldPat: LastPattern<FieldPattern<'ast>> = {
FieldPattern => LastPattern::Normal(<>),
".." <Ident?> => LastPattern::Ellipsis(<>),
};
// Last pattern of an array pattern. See `LastFieldPat`.
LastElemPat: LastPattern<Pattern<'ast>> = {
Pattern => LastPattern::Normal(<>),
".." <Ident?> => LastPattern::Ellipsis(<>),
}
// A default annotation in a pattern.
DefaultAnnot: Ast<'ast> = "?" <Term>;
// A metadata keyword returned as an indent. In some positions, those are
// considered valid identifiers. See ExtendedIdent below.
MetadataKeyword: Ident = {
"doc" => Ident::new("doc"),
"default" => Ident::new("default"),
"force" => Ident::new("force"),
"priority" => Ident::new("priority"),
"optional" => Ident::new("optional"),
"not_exported" => Ident::new("not_exported"),
};
// We allow metadata keywords (optional, default, doc, etc.) as field names
// because:
//
// 1. There are many metadata keywords, and it's annoying to quote them all
// (and they might be growing, which is causing backward compatibility issues)
// 2. Metadata keyword can't appear anywhere in field position (and vice-versa), so there's no clash.
//
// Thus, for fields, ExtendedIdent is used in place of Ident.
ExtendedIdent: LocIdent = {
SpannedId<MetadataKeyword>,
Ident,
};
// The "or" contextual keyword, parsed as an indent.
IdentOr: Ident = "or" => Ident::new("or");
// The "as" contextual keyword, parsed as an indent.
IdentAs: Ident = "as" => Ident::new("as");
// The "include" contextual keyword, parsed as an indent.
IdentInclude: Ident = "include" => Ident::new("include");
// The set of pure identifiers, which are never keywords in any context.
RestrictedIdent: Ident = "identifier" => Ident::new(<>);
// Identifiers allowed everywhere, which includes pure identifiers and contextual
// keywords.
#[inline]
Ident: LocIdent = {
SpannedId<IdentOr>,
SpannedId<IdentAs>,
SpannedId<IdentInclude>,
SpannedId<RestrictedIdent>,
};
Bool: bool = {
"true" => true,
"false" => false,
};
// String-like syntax which supports interpolation.
//
// Depending on the opening brace, these either parse as strings, or as
// "symbolic strings", which get desugared here to an array of terms.
StringChunks: Node<'ast> = {
// The lexer emits a stream of groups of `ChunkTerm` interspersed by one
// `ChunkLiteral`: consecutive chunks literals are fused by the lexer.
<start: StringStart> <fst: ChunkLiteral?> <chunks: (ChunkTerm+ChunkLiteral)*> <lasts:ChunkTerm*> <end: StringEnd> => {
debug_assert!(
start.is_closed_by(&end),
"Fatal parser error: a string starting with {start:?} should never be closed by {end:?}"
);
let mut chunks: Vec<StringChunk<Ast<'ast>>> = fst.into_iter()
.map(StringChunk::Literal)
.chain(chunks.into_iter()
.map(|(mut es, s)| {
es.push(StringChunk::Literal(s));
es
})
.flatten())
.chain(lasts.into_iter())
.collect();
if start.needs_strip_indent() {
strip_indent(&mut chunks);
}
// In the case of symbolic strings, we don't produce a string (in
// practice string chunks). The chunks are reified to an Nickel array
// and wrapped in a record instead.
if let StringStartDelimiter::Symbolic(prefix) = start {
let terms = chunks.into_iter().map(|chunk| match chunk {
StringChunk::Literal(_) => alloc.string_chunks(iter::once(chunk)).into(),
StringChunk::Expr(e, _) => e,
});
builder::Record::new()
.field("tag")
.value(alloc, builder::enum_tag("SymbolicString"))
.field("prefix")
.value(alloc, builder::enum_tag(prefix))
.field("fragments")
.value(alloc, alloc.array(terms))
.build(alloc)
.node
} else {
alloc.string_chunks(chunks)
}
},
};
StringStart : StringStartDelimiter<'input> = {
"\"" => StringStartDelimiter::Standard,
"m%\"" => StringStartDelimiter::Multiline,
"symbolic string start" => StringStartDelimiter::Symbolic(<>.0),
};
StringEnd : StringEndDelimiter = {
"\"" => StringEndDelimiter::Standard,
"\"%" => StringEndDelimiter::Special,
};
ChunkLiteral : String =
<parts: ChunkLiteralPart+> => {
parts.into_iter().fold(String::new(), |mut acc, part| {
match part {
ChunkLiteralPart::Str(s) => acc.push_str(&s),
ChunkLiteralPart::Char(c) => acc.push(c),
};
acc
})
};
// An interpolated expression in a string: `%{<exp>}`.
ChunkTerm: StringChunk<Ast<'ast>> = Interpolation <Term> "}" => StringChunk::Expr(<>, 0);
// The opening sequence of string interpolation.
Interpolation = { "%{", "multstr %{" };
// A construct which looks like a string, but is generic over its delimiters.
// Used to implement `StaticString` as well as `StringEnumTag`.
DelimitedStaticString<Start, End>: String = Start <s: ChunkLiteral?> End => s.unwrap_or_default();
// A static string using the basic string syntax (delimited by double quotes).
StandardStaticString = DelimitedStaticString<"\"", "\"">;
// A static string using the multiline string syntax.
MultilineStaticString: String = DelimitedStaticString<"m%\"","\"%"> => {
// strip the common indentation prefix
let mut chunks = vec![StringChunk::Literal(<>)];
strip_indent(&mut chunks);
// unwrap(): we crated the vector just above with exactly one element, and
// `strip_indent` doesn't change the size of its vector argument, so there's
// still exactly one element
match chunks.pop().unwrap() {
StringChunk::Literal(s) => s,
// unreachable: we built the only element as a `StringChunk::Literal`,
// and `strip_indent` doesn't change the nature of chunks, so the only
// element can't be anything else (an expression)
_ => unreachable!(),
}
};
// A string which must be known statically without having to run the program. In
// practice, it's a string where interpolation isn't allowed.
StaticString: String = {
StandardStaticString,
MultilineStaticString,
}
// A quoted enum tag, which can contain spaces or other special characters.
StringEnumTag = DelimitedStaticString<"'\"", "\"">;
EnumTag: LocIdent = {
"raw enum tag" => <>.into(),
StringEnumTag => <>.into(),
};
ChunkLiteralPart: ChunkLiteralPart = {
"str literal" => ChunkLiteralPart::Str(<>),
"multstr literal" => ChunkLiteralPart::Str(<>),
"str esc char" => ChunkLiteralPart::Char(<>),
};
UOp: PrimOp = {
"typeof" => PrimOp::Typeof,
"cast" => PrimOp::Cast,
"blame" => PrimOp::Blame,
"label/flip_polarity" => PrimOp::LabelFlipPol,
"label/polarity" => PrimOp::LabelPol,
"label/go_dom" => PrimOp::LabelGoDom,
"label/go_codom" => PrimOp::LabelGoCodom,
"label/go_array" => PrimOp::LabelGoArray,
"label/go_dict" => PrimOp::LabelGoDict,
"enum/embed" <Ident> => PrimOp::EnumEmbed(<>),
"array/map" => PrimOp::ArrayMap,
"array/generate" => PrimOp::ArrayGen,
"record/map" => PrimOp::RecordMap,
"seq" => PrimOp::Seq,
"deep_seq" => PrimOp::DeepSeq,
"op force" => PrimOp::Force{ ignore_not_exported: false },
"array/length" => PrimOp::ArrayLength,
"record/fields" => PrimOp::RecordFields(RecordOpKind::IgnoreEmptyOpt),
"record/fields_with_opts" => PrimOp::RecordFields(RecordOpKind::ConsiderAllFields),
"record/values" => PrimOp::RecordValues,
"string/trim" => PrimOp::StringTrim,
"string/chars" => PrimOp::StringChars,
"string/uppercase" => PrimOp::StringUppercase,
"string/lowercase" => PrimOp::StringLowercase,
"string/length" => PrimOp::StringLength,
"to_string" => PrimOp::ToString,
"number/from_string" => PrimOp::NumberFromString,
"enum/from_string" => PrimOp::EnumFromString,
"string/is_match" => PrimOp::StringIsMatch,
"string/find" => PrimOp::StringFind,
"string/find_all" => PrimOp::StringFindAll,
// Currently recursive priorities are disabled (since 1.2.0).
// "op rec_force" => PrimOp::RecForce,
// "op rec_default" => PrimOp::RecDefault,
"record/empty_with_tail" => PrimOp::RecordEmptyWithTail,
"record/freeze" => PrimOp::RecordFreeze,
"trace" => PrimOp::Trace,
"label/push_diag" => PrimOp::LabelPushDiag,
<l: @L> "eval_nix" <r: @R> =>? {
#[cfg(feature = "nix-experimental")]
{
Ok(PrimOp::EvalNix)
}
#[cfg(not(feature = "nix-experimental"))]
{
Err(ParseError::DisabledFeature {
feature: String::from("nix-experimental"),
span: mk_span(src_id, l, r),
}.into()
)
}
},
"enum/get_arg" => PrimOp::EnumGetArg,
"enum/make_variant" => PrimOp::EnumMakeVariant,
"enum/is_variant" => PrimOp::EnumIsVariant,
"enum/get_tag" => PrimOp::EnumGetTag,
"contract/custom" => PrimOp::ContractCustom,
"number/arccos" => PrimOp::NumberArcCos,
"number/arcsin" => PrimOp::NumberArcSin,
"number/arctan" => PrimOp::NumberArcTan,
"number/cos" => PrimOp::NumberCos,
"number/sin" => PrimOp::NumberSin,
"number/tan" => PrimOp::NumberTan,
}
PatternGuard: Ast<'ast> = "if" <Term> => <>;
MatchBranch: MatchBranch<'ast> =
<pattern: Pattern> <guard: PatternGuard?> "=>" <body: Term> =>
MatchBranch { pattern, guard, body};
// Infix operators by precedence levels. Lowest levels take precedence over
// highest ones.
InfixBOp2: PrimOp = {
"++" => PrimOp::StringConcat,
"@" => PrimOp::ArrayConcat,
}
InfixBOp3: PrimOp = {
"*" => PrimOp::Mult,
"/" => PrimOp::Div,
"%" => PrimOp::Modulo,
}
InfixBOp4: PrimOp = {
"+" => PrimOp::Plus,
"-" => PrimOp::Sub,
}
InfixUOp5: PrimOp = {
"!" => PrimOp::BoolNot,
}
InfixBOp6: PrimOp = {
"&" => PrimOp::Merge(MergeKind::Standard),
}
InfixBOp7: PrimOp = {
"<" => PrimOp::LessThan,
"<=" => PrimOp::LessOrEq,
">" => PrimOp::GreaterThan,
">=" => PrimOp::GreaterOrEq,
}
InfixBOp8: PrimOp = {
"==" => PrimOp::Eq,
}
InfixLazyBOp9: PrimOp = {
"&&" => PrimOp::BoolAnd,
}
InfixLazyBOp10: PrimOp = {
"||" => PrimOp::BoolOr,
}
InfixBOp: PrimOp = {
InfixBOp2,
InfixBOp3,
InfixBOp4,
InfixBOp6,
InfixBOp7,
InfixBOp8,
}
InfixUOpOrLazyBOp: PrimOp = {
InfixUOp5,
InfixLazyBOp9,
InfixLazyBOp10,
}
InfixOp: InfixOp = {
InfixBOp => InfixOp(<>),
InfixUOpOrLazyBOp => InfixOp(<>),
}
EtaExpand<Op>: Node<'ast> = <l: @L> <op: Op> <r: @R> =>
op.eta_expand(alloc, mk_pos(src_id, l, r));
// Infix ops that are desugared away but for which we still need support the
// curried operator syntax.
ExtendedInfixOp: ExtendedInfixOp = {
"|>" => ExtendedInfixOp::ReverseApp,
"!=" => ExtendedInfixOp::NotEqual,
}
DotAsInfixOp: InfixOp = "." => InfixOp(PrimOp::RecordGet);
CurriedOp: Node<'ast> = {
EtaExpand<InfixOp>,
EtaExpand<ExtendedInfixOp>,
EtaExpand<DotAsInfixOp>,
}
InfixUOpApp<UOp, Term>: UniTerm<'ast> =
<op: UOp> <e: AsTerm<Term>> => UniTerm::from(alloc.prim_op(op, iter::once(e)));
InfixBOpApp<BOp, LTerm, RTerm>: UniTerm<'ast> =
<e1: AsTerm<LTerm>> <op: BOp> <e2: AsTerm<RTerm>> =>
UniTerm::from(primop_app!(alloc, op, e1, e2));
InfixLazyBOpApp<UOp, LTerm, RTerm>: UniTerm<'ast> =
<e1: AsTerm<LTerm>> <op: UOp> <e2: AsTerm<RTerm>> =>
UniTerm::from(app!(alloc, primop_app!(alloc, op, e1), e2));
InfixExpr: UniTerm<'ast> = {
#[precedence(level="0")]
Applicative,
#[precedence(level="1")]
"-" <AsTerm<InfixExpr>> =>
UniTerm::from(primop_app!(alloc, PrimOp::Sub, alloc.number(Number::ZERO), <>)),
#[precedence(level="2")] #[assoc(side="left")]
InfixBOpApp<InfixBOp2, InfixExpr, InfixExpr>,
#[precedence(level="3")] #[assoc(side="left")]
InfixBOpApp<InfixBOp3, InfixExpr, InfixExpr>,
#[precedence(level="4")] #[assoc(side="left")]
InfixBOpApp<InfixBOp4, InfixExpr, InfixExpr>,
#[precedence(level="5")]
InfixUOpApp<InfixUOp5, InfixExpr>,
#[precedence(level="6")] #[assoc(side="left")]
InfixBOpApp<InfixBOp6, InfixExpr, InfixExpr>,
<t1: AsTerm<InfixExpr>> "|>" <t2: AsTerm<InfixExpr>> =>
UniTerm::from(app!(alloc, t2, t1)),
#[precedence(level="7")] #[assoc(side="left")]
InfixBOpApp<InfixBOp7, InfixExpr, InfixExpr>,
#[precedence(level="8")] #[assoc(side="left")]
InfixBOpApp<InfixBOp8, InfixExpr, InfixExpr>,
<t1: AsTerm<InfixExpr>> "!=" <t2: AsTerm<InfixExpr>> =>
UniTerm::from(
primop_app!(
alloc,
PrimOp::BoolNot,
primop_app!(alloc, PrimOp::Eq, t1, t2),
)
),
#[precedence(level="9")] #[assoc(side="left")]
InfixLazyBOpApp<InfixLazyBOp9, InfixExpr, InfixExpr>,
#[precedence(level="10")] #[assoc(side="left")]
InfixLazyBOpApp<InfixLazyBOp10, InfixExpr, InfixExpr>,
#[precedence(level="11")] #[assoc(side="right")]
<s: AsType<InfixExpr>> "->" <t: AsType<InfixExpr>> =>
UniTerm::from(Type::from(TypeF::Arrow(alloc.alloc(s), alloc.alloc(t)))),
}
BOpPre: PrimOp = {
"contract/apply" => PrimOp::ContractApply,
"contract/check" => PrimOp::ContractCheck,
"contract/array_lazy_app" => PrimOp::ContractArrayLazyApp,
"contract/record_lazy_app" => PrimOp::ContractRecordLazyApp,
"seal" => PrimOp::Seal,
"unseal" => PrimOp::Unseal,
"label/go_field" => PrimOp::LabelGoField,
"record/has_field" => PrimOp::RecordHasField(RecordOpKind::IgnoreEmptyOpt),
"record/has_field_with_opts" => PrimOp::RecordHasField(RecordOpKind::ConsiderAllFields),
"record/field_is_defined" => PrimOp::RecordFieldIsDefined(RecordOpKind::IgnoreEmptyOpt),
"record/field_is_defined_with_opts" => PrimOp::RecordFieldIsDefined(RecordOpKind::ConsiderAllFields),
"array/at" => PrimOp::ArrayAt,
"hash" => PrimOp::Hash,
"serialize" => PrimOp::Serialize,
"deserialize" => PrimOp::Deserialize,
"number/arctan2" => PrimOp::NumberArcTan2,
"number/log" => PrimOp::NumberLog,
"pow" => PrimOp::Pow,
"string/split" => PrimOp::StringSplit,
"string/contains" => PrimOp::StringContains,
"string/compare" => PrimOp::StringCompare,
"string/base64_encode" => PrimOp::StringBase64Encode,
"string/base64_decode" => PrimOp::StringBase64Decode,
"record/insert" => PrimOp::RecordInsert(RecordOpKind::IgnoreEmptyOpt),
"record/insert_with_opts" => PrimOp::RecordInsert(RecordOpKind::ConsiderAllFields),
"record/remove" => PrimOp::RecordRemove(RecordOpKind::IgnoreEmptyOpt),
"record/remove_with_opts" => PrimOp::RecordRemove(RecordOpKind::ConsiderAllFields),
"record/split_pair" => PrimOp::RecordSplitPair,
"record/disjoint_merge" => PrimOp::RecordDisjointMerge,
"label/with_message" => PrimOp::LabelWithMessage,
"label/with_notes" => PrimOp::LabelWithNotes,
"label/append_note" => PrimOp::LabelAppendNote,
"label/lookup_type_variable" => PrimOp::LabelLookupTypeVar,
}
NOpPre<ArgRule>: UniTerm<'ast> = {
"string/replace" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::StringReplace, t1, t2, t3)),
"string/replace_regex" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::StringReplaceRegex, t1, t2, t3)),
"string/substr" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::StringSubstr, t1, t2, t3)),
"record/seal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> <t4: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::RecordSealTail, t1, t2, t3, t4)),
"record/unseal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::RecordUnsealTail, t1, t2, t3)),
"label/insert_type_variable" <key: ArgRule> <pol: ArgRule> <label: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::LabelInsertTypeVar, key, pol, label)),
"array/slice" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::ArraySlice, t1, t2, t3)),
"record/merge_contract" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
UniTerm::from(primop_app!(alloc, PrimOp::MergeContract, t1, t2, t3)),
}
TypeBuiltin: TypeUnr<'ast> = {
"Dyn" => TypeF::Dyn,
"Number" => TypeF::Number,
"Bool" => TypeF::Bool,
"String" => TypeF::String,
}
TypeEnumRow: EnumRow<'ast> = <id: EnumTag> <typ: (AsType<Atom>)?> => {
EnumRow {
id,
typ: typ.map(|ty| alloc.alloc(ty)),
}
};
TypeEnum: TypeUnr<'ast> = "[|" <rows:(<TypeEnumRow> ",")*> <last: (<TypeEnumRow>)?> <tail: (";" <Ident>)?> "|]" => {
let ty = rows.into_iter()
.chain(last.into_iter())
// As we build row types as a linked list via a fold on the original
// iterator, the order of identifiers is reversed. This not a big deal
// but it's less confusing to the user to print them in the original
// order for error reporting.
.rev()
.fold(
match tail {
Some(id) => EnumRowsF::TailVar(id),
None => EnumRowsF::Empty,
}
,
|erows, row| {
EnumRowsF::Extend {
row,
tail: alloc.enum_rows(erows)
}
}
);
TypeF::Enum(EnumRows(ty))
};
TypeAtom: TypeUnr<'ast> = {
TypeBuiltin,
TypeEnum,
"{" "_" ":" <Type> "}" => {
TypeF::Dict {
type_fields: alloc.alloc(<>),
flavour: DictTypeFlavour::Type
}
},
// Although dictionary contracts aren't really types, we treat them as
// types for now - at least syntactically - as they are represented using a
// `TypeF::Dict` constructor in the AST. This just simpler for many reasons
// (error reporting of contracts and in particular type paths, LSP, and so
// on.)
//
// However, note that we use a fixed type as an argument. This has the
// effect of preventing dictionary contracts from capturing type variables
// that could be in scope. For example, we want `forall a. {_ | a}` to fail
// (see https://github.com/tweag/nickel/issues/1228). Fixing type variables
// right away inside the dictionary contract (before the enclosing `forall`
// is fixed) will indeed turn it into a term variable, and raise an unbound
// type variable error.
"{" "_" "|" <FixedType> "}" => {
TypeF::Dict {
type_fields: alloc.alloc(<>),
flavour: DictTypeFlavour::Contract
}
},
"_" => {
let id = *next_wildcard_id;
*next_wildcard_id += 1;
TypeF::Wildcard(id)
},
}
SignedNumLiteral: Number = <sign: "-"?> <value: NumberLiteral> => {
if sign.is_some() {
-value
} else {
value
}
};
extern {
type Location = usize;
type Error = ParseOrLexError;
enum Token<'input> {
"identifier" => Token::Normal(NormalToken::Identifier(<&'input str>)),
"str literal" => Token::Str(StringToken::Literal(<String>)),
"str esc char" => Token::Str(StringToken::EscapedChar(<char>)),
"multstr literal" => Token::MultiStr(MultiStringToken::Literal(<String>)),
"dec num literal" => Token::Normal(NormalToken::DecNumLiteral(<Number>)),
"hex num literal" => Token::Normal(NormalToken::HexNumLiteral(<Number>)),
"oct num literal" => Token::Normal(NormalToken::OctNumLiteral(<Number>)),
"bin num literal" => Token::Normal(NormalToken::BinNumLiteral(<Number>)),
"raw enum tag" => Token::Normal(NormalToken::RawEnumTag(<&'input str>)),
"'\"" => Token::Normal(NormalToken::StrEnumTagBegin),
"if" => Token::Normal(NormalToken::If),
"then" => Token::Normal(NormalToken::Then),
"else" => Token::Normal(NormalToken::Else),
"forall" => Token::Normal(NormalToken::Forall),
"in" => Token::Normal(NormalToken::In),
"let" => Token::Normal(NormalToken::Let),
"rec" => Token::Normal(NormalToken::Rec),
"match" => Token::Normal(NormalToken::Match),
"null" => Token::Normal(NormalToken::Null),
"true" => Token::Normal(NormalToken::True),
"false" => Token::Normal(NormalToken::False),
"or" => Token::Normal(NormalToken::Or),
"as" => Token::Normal(NormalToken::As),
"include" => Token::Normal(NormalToken::Include),
"?" => Token::Normal(NormalToken::QuestionMark),
"," => Token::Normal(NormalToken::Comma),
";" => Token::Normal(NormalToken::Semicolon),
":" => Token::Normal(NormalToken::Colon),
"$" => Token::Normal(NormalToken::Dollar),
"=" => Token::Normal(NormalToken::Equals),
"!=" => Token::Normal(NormalToken::NotEquals),
"&" => Token::Normal(NormalToken::Ampersand),
"." => Token::Normal(NormalToken::Dot),
"%{" => Token::Str(StringToken::Interpolation),
"multstr %{" => Token::MultiStr(MultiStringToken::Interpolation),
"+" => Token::Normal(NormalToken::Plus),
"-" => Token::Normal(NormalToken::Minus),
"*" => Token::Normal(NormalToken::Times),
"/" => Token::Normal(NormalToken::Div),
"%" => Token::Normal(NormalToken::Percent),
"++" => Token::Normal(NormalToken::DoublePlus),
"==" => Token::Normal(NormalToken::DoubleEq),
"@" => Token::Normal(NormalToken::At),
"&&" => Token::Normal(NormalToken::DoubleAnd),
"||" => Token::Normal(NormalToken::DoublePipe),
"!" => Token::Normal(NormalToken::Bang),
".." => Token::Normal(NormalToken::Ellipsis),
"fun" => Token::Normal(NormalToken::Fun),
"import" => Token::Normal(NormalToken::Import),
"|" => Token::Normal(NormalToken::Pipe),
"|>" => Token::Normal(NormalToken::RightPipe),
"->" => Token::Normal(NormalToken::SimpleArrow),
"=>" => Token::Normal(NormalToken::DoubleArrow),
"_" => Token::Normal(NormalToken::Underscore),
"\"" => Token::Normal(NormalToken::DoubleQuote),
"\"%" => Token::MultiStr(MultiStringToken::End),
"m%\"" => Token::Normal(NormalToken::MultiStringStart(<usize>)),
"symbolic string start" => Token::Normal(NormalToken::SymbolicStringStart(
SymbolicStringStart{prefix: <&'input str>, length: <usize>})),
"Number" => Token::Normal(NormalToken::Number),
"Dyn" => Token::Normal(NormalToken::Dyn),
"String" => Token::Normal(NormalToken::String),
"Bool" => Token::Normal(NormalToken::Bool),
"Array" => Token::Normal(NormalToken::Array),
"typeof" => Token::Normal(NormalToken::Typeof),
"cast" => Token::Normal(NormalToken::Cast),
"contract/apply" => Token::Normal(NormalToken::ContractApply),
"contract/check" => Token::Normal(NormalToken::ContractCheck),
"contract/array_lazy_app" => Token::Normal(NormalToken::ContractArrayLazyApp),
"contract/record_lazy_app" => Token::Normal(NormalToken::ContractRecordLazyApp),
"contract/custom" => Token::Normal(NormalToken::ContractCustom),
"op force" => Token::Normal(NormalToken::OpForce),
"blame" => Token::Normal(NormalToken::Blame),
"label/flip_polarity" => Token::Normal(NormalToken::LabelFlipPol),
"label/polarity" => Token::Normal(NormalToken::LabelPol),
"label/go_dom" => Token::Normal(NormalToken::LabelGoDom),
"label/go_codom" => Token::Normal(NormalToken::LabelGoCodom),
"label/go_array" => Token::Normal(NormalToken::LabelGoArray),
"label/go_dict" => Token::Normal(NormalToken::LabelGoDict),
"label/go_field" => Token::Normal(NormalToken::LabelGoField),
"seal" => Token::Normal(NormalToken::Seal),
"unseal" => Token::Normal(NormalToken::Unseal),
"enum/embed" => Token::Normal(NormalToken::EnumEmbed),
"record/map" => Token::Normal(NormalToken::RecordMap),
"record/empty_with_tail" => Token::Normal(NormalToken::RecordEmptyWithTail),
"record/insert" => Token::Normal(NormalToken::RecordInsert),
"record/insert_with_opts" => Token::Normal(NormalToken::RecordInsertWithOpts),
"record/remove" => Token::Normal(NormalToken::RecordRemove),
"record/remove_with_opts" => Token::Normal(NormalToken::RecordRemoveWithOpts),
"record/seal_tail" => Token::Normal(NormalToken::RecordSealTail),
"record/unseal_tail" => Token::Normal(NormalToken::RecordUnsealTail),
"seq" => Token::Normal(NormalToken::Seq),
"deep_seq" => Token::Normal(NormalToken::DeepSeq),
"array/length" => Token::Normal(NormalToken::ArrayLength),
"record/fields" => Token::Normal(NormalToken::RecordFields),
"record/fields_with_opts" => Token::Normal(NormalToken::RecordFieldsWithOpts),
"record/values" => Token::Normal(NormalToken::RecordValues),
"number/arccos" => Token::Normal(NormalToken::NumberArcCos),
"number/arcsin" => Token::Normal(NormalToken::NumberArcSin),
"number/arctan" => Token::Normal(NormalToken::NumberArcTan),
"number/arctan2" => Token::Normal(NormalToken::NumberArcTan2),
"number/cos" => Token::Normal(NormalToken::NumberCos),
"number/sin" => Token::Normal(NormalToken::NumberSin),
"number/tan" => Token::Normal(NormalToken::NumberTan),
"number/log" => Token::Normal(NormalToken::NumberLog),
"pow" => Token::Normal(NormalToken::Pow),
"op rec_force" => Token::Normal(NormalToken::OpRecForce),
"op rec_default" => Token::Normal(NormalToken::OpRecDefault),
"trace" => Token::Normal(NormalToken::Trace),
"label/insert_type_variable" => Token::Normal(NormalToken::LabelInsertTypeVar),
"label/lookup_type_variable" => Token::Normal(NormalToken::LabelLookupTypeVar),
"record/has_field" => Token::Normal(NormalToken::RecordHasField),
"record/has_field_with_opts" => Token::Normal(NormalToken::RecordHasFieldWithOpts),
"record/field_is_defined" => Token::Normal(NormalToken::RecordFieldIsDefined),
"record/field_is_defined_with_opts" => Token::Normal(NormalToken::RecordFieldIsDefinedWithOpts),
"record/split_pair" => Token::Normal(NormalToken::RecordSplitPair),
"record/disjoint_merge" => Token::Normal(NormalToken::RecordDisjointMerge),
"record/merge_contract" => Token::Normal(NormalToken::RecordMergeContract),
"record/freeze" => Token::Normal(NormalToken::RecordFreeze),
"array/map" => Token::Normal(NormalToken::ArrayMap),
"array/generate" => Token::Normal(NormalToken::ArrayGen),
"array/at" => Token::Normal(NormalToken::ArrayAt),
"default" => Token::Normal(NormalToken::Default),
"force" => Token::Normal(NormalToken::Force),
"doc" => Token::Normal(NormalToken::Doc),
"optional" => Token::Normal(NormalToken::Optional),
"priority" => Token::Normal(NormalToken::Priority),
"not_exported" => Token::Normal(NormalToken::NotExported),
"hash" => Token::Normal(NormalToken::OpHash),
"serialize" => Token::Normal(NormalToken::Serialize),
"deserialize" => Token::Normal(NormalToken::Deserialize),
"string/split" => Token::Normal(NormalToken::StringSplit),
"string/trim" => Token::Normal(NormalToken::StringTrim),
"string/chars" => Token::Normal(NormalToken::StringChars),
"string/uppercase" => Token::Normal(NormalToken::StringUppercase),
"string/lowercase" => Token::Normal(NormalToken::StringLowercase),
"string/contains" => Token::Normal(NormalToken::StringContains),
"string/compare" => Token::Normal(NormalToken::StringCompare),
"string/replace" => Token::Normal(NormalToken::StringReplace),
"string/replace_regex" => Token::Normal(NormalToken::StringReplaceRegex),
"string/is_match" => Token::Normal(NormalToken::StringIsMatch),
"string/find" => Token::Normal(NormalToken::StringFind),
"string/find_all" => Token::Normal(NormalToken::StringFindAll),
"string/length" => Token::Normal(NormalToken::StringLength),
"string/substr" => Token::Normal(NormalToken::StringSubstr),
"string/base64_encode" => Token::Normal(NormalToken::StringBase64Encode),
"string/base64_decode" => Token::Normal(NormalToken::StringBase64Decode),
"to_string" => Token::Normal(NormalToken::ToString),
"number/from_string" => Token::Normal(NormalToken::NumberFromString),
"enum/from_string" => Token::Normal(NormalToken::EnumFromString),
"label/with_message" => Token::Normal(NormalToken::LabelWithMessage),
"label/with_notes" => Token::Normal(NormalToken::LabelWithNotes),
"label/append_note" => Token::Normal(NormalToken::LabelAppendNote),
"label/push_diag" => Token::Normal(NormalToken::LabelPushDiag),
"array/slice" => Token::Normal(NormalToken::ArraySlice),
"eval_nix" => Token::Normal(NormalToken::EvalNix),
"enum/get_arg" => Token::Normal(NormalToken::EnumGetArg),
"enum/make_variant" => Token::Normal(NormalToken::EnumMakeVariant),
"enum/is_variant" => Token::Normal(NormalToken::EnumIsVariant),
"enum/get_tag" => Token::Normal(NormalToken::EnumGetTag),
"{" => Token::Normal(NormalToken::LBrace),
"}" => Token::Normal(NormalToken::RBrace),
"[" => Token::Normal(NormalToken::LBracket),
"]" => Token::Normal(NormalToken::RBracket),
"(" => Token::Normal(NormalToken::LParen),
")" => Token::Normal(NormalToken::RParen),
"<" => Token::Normal(NormalToken::LAngleBracket),
"<=" => Token::Normal(NormalToken::LessOrEq),
">" => Token::Normal(NormalToken::RAngleBracket),
">=" => Token::Normal(NormalToken::GreaterOrEq),
"[|" => Token::Normal(NormalToken::EnumOpen),
"|]" => Token::Normal(NormalToken::EnumClose),
}
}