//
// Copyright Cedar Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::str::FromStr;
use std::sync::Arc;
use crate::validator::cedar_schema::err::{RawErrorRecovery, UserError};
use crate::parser::{Node, Loc, unescape::to_unescaped_string, cst::Ref};
use crate::ast::{Id, AnyId, Annotations};
use smol_str::SmolStr;
use smol_str::ToSmolStr;
use crate::validator::cedar_schema::ast::{
Path,
EntityDecl,
StandardEntityDecl,
EnumEntityDecl,
Declaration,
Namespace,
Schema as ASchema,
Type as SType,
AttrDecl,
ActionDecl,
PR,
AppDecl,
TypeDecl,
PrimitiveType,
QualName,
PRAppDecl,
deduplicate_annotations,
Annotated,
};
use nonempty::{NonEmpty, nonempty};
use itertools::Either;
use std::collections::BTreeMap;
use lalrpop_util::{ParseError, ErrorRecovery};
/// `errors` collects generated errors.
///
/// `src` is the (full) original source being parsed, which the source locations l,r index into.
grammar<'err, 's>(errors: &'err mut Vec<RawErrorRecovery<'input>>, src: &'s Arc<str>, keep_src: bool);
extern {
type Error = UserError;
}
match {
// Whitespace and comments
r"\s*" => { }, // The default whitespace skipping is disabled an `ignore pattern` is specified
r"//[^\n\r]*[\n\r]*" => { }, // Skip `// comments`
// keywords
"namespace" => NAMESPACE,
"entity" => ENTITY,
"in" => IN,
"type" => TYPE,
"Set" => SET,
"appliesTo" => APPLIESTO,
"principal" => PRINCIPAL,
"action" => ACTION,
"resource" => RESOURCE,
"context" => CONTEXT,
"attributes" => ATTRIBUTES,
"tags" => TAGS,
"Long" => LONG,
"String" => STRING,
"Bool" => BOOL,
"enum" => ENUM,
// data input
r"[_a-zA-Z][_a-zA-Z0-9]*" => IDENTIFIER,
// The `NUMBER` token is a positive integer.
// Negative number literals are negation operations.
r"[0-9]+" => NUMBER,
r#""(\\.|[^"\\])*""# => STRINGLIT,
// other tokens
",", ";", ":", "::", "{", "}", "[", "]",
"<", ">", "=", "?", "@", "(", ")",
}
// Annotations := {'@' AnyIdent '(' String ')'}
Annotation: Node<(Node<AnyId>, Option<Node<SmolStr>>)> = {
<l:@L> "@" <key:AnyIdent> <value: ("(" <STR> ")")?> <r:@R> => Node::with_source_loc((key, value), Loc::new(l..r, Arc::clone(src)))
}
Annotated<E>: Annotated<E> = {
<annotations: Annotation*> <e:E> =>? {
Ok(deduplicate_annotations(e, annotations)?)
},
}
// Schema := {Namespace}
pub Schema: ASchema = {
<ns: Namespace*> => ns,
}
#[inline]
Namedspace: Namespace = {
<l:@L> NAMESPACE <p: Path> "{" <decls: Annotated<Decl>*> "}" <r:@R>
=> Namespace { name: Some(p), decls, loc: Some(Loc::new(l..r, Arc::clone(src)))},
}
// Namespace := 'namespace' Path '{' {Decl} '}'
Namespace: Annotated<Namespace> = {
<ns: Annotated<Namedspace>> => ns,
<l:@L> <decl: Annotated<Decl>> <r:@R> => Annotated {data: Namespace {name: None, decls: vec![decl], loc: Some(Loc::new(l..r, Arc::clone(src)))}, annotations: Annotations::new()},
}
// Decl := Entity | Action | TypeDecl
Decl: Node<Declaration> = {
<e:Entity> => e,
<a:Action> => a,
<t:TypeDecl> => t,
}
// Entity := 'entity' Idents ['in' EntTypes] [['='] RecType] ';' <r:@R>
Entity: Node<Declaration> = {
<l1:@L> ENTITY <ets: Idents> <ps:(IN <EntTypes>)?> <l2:@L> <ds:("="? "{" <AttrDecls?> "}")?> <r2:@R> <ts:(TAGS <Type>)?> ";" <r1:@R>
=> Node::with_source_loc(Declaration::Entity(EntityDecl::Standard(StandardEntityDecl {
names: ets,
member_of_types: ps.unwrap_or_default(),
attrs: Node::with_source_loc(ds.map(|ds| ds.unwrap_or_default()).unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))),
tags: ts,
})), Loc::new(l1..r1, Arc::clone(src))),
<l:@L> ENTITY <ets: Idents> ENUM "[" <choices: STRs> "]" ";" <r:@R> => Node::with_source_loc(Declaration::Entity(EntityDecl::Enum(EnumEntityDecl {
names: ets,
choices,
})), Loc::new(l..r, Arc::clone(src))),
}
// Action := 'action' Names ['in' QualNameOrNames]
Action: Node<Declaration> = {
<l:@L> ACTION <ns:Names> <ps:(IN <QualNameOrQualNames>)?> <ads:(APPLIESTO "{" <AppDecls> "}")?> <attrs:(ATTRIBUTES "{" "}")?>";" <r:@R>
=> Node::with_source_loc(Declaration::Action(ActionDecl { names: ns, parents: ps, app_decls: ads}), Loc::new(l..r, Arc::clone(src))),
}
TypeDecl: Node<Declaration> = {
<l:@L> TYPE <i:Ident> "=" <t:Type> ";" <r:@R>
=> Node::with_source_loc(Declaration::Type(TypeDecl { name : i, def : t}), Loc::new(l..r, Arc::clone(src))),
}
// AppDecls := ('principal' | 'resource') ':' EntTypes [',' | ',' AppDecls]
// | 'context' ':' (Path | RecType) [',' | ',' AppDecls]
AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
<l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> <r:@R> ","?
=> {
let entity_tys: Option<NonEmpty<Path>> = NonEmpty::collect(ets.into_iter());
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys }), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src)))
},
<l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> <r:@R> "," <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
let entity_tys: Option<NonEmpty<Path>> = NonEmpty::collect(ets.into_iter());
ds.insert(0, Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys }), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(ds, Loc::new(l..r, Arc::clone(src)))
},
<l:@L> CONTEXT ":" <p:Path> ","? <r:@R>
=> Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" <p:Path> "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
},
<l1:@L> CONTEXT ":" <l2:@L> "{" <attrs:AttrDecls?> "}" <r2:@R> ","? <r1:@R>
=>
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(Either::Right(Node::with_source_loc(attrs.unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))))), Loc::new(l1..r1, Arc::clone(src)))],
Loc::new(l1..r1, Arc::clone(src))),
<l1:@L> CONTEXT ":" <l2:@L> "{" <attrs:AttrDecls?> "}" <r2:@R> "," <r1:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Right(Node::with_source_loc(attrs.unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))))), Loc::new(l1..r1, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l1..r1, Arc::clone(src)))
},
}
// SetType := 'Set' '<' Type '>'
// RecType := '{' [AttrDecls] '}'
// Type := PRIMTYPE | Path | SetType | RecType
pub Type: Node<SType> = {
<p:Path>
=> { let loc = p.loc().cloned(); Node::with_maybe_source_loc(SType::Ident(p), loc) },
<l:@L> SET "<" <t:Type> ">" <r:@R>
=> Node::with_source_loc(SType::Set(Box::new(t)), Loc::new(l..r, Arc::clone(src))),
<l:@L> "{" <ds:AttrDecls?> "}" <r:@R>
=> Node::with_source_loc(SType::Record(ds.unwrap_or_default()), Loc::new(l..r, Arc::clone(src))),
}
// AttrDecls := Annotation* Name ['?'] ':' Type [',' | ',' AttrDecls]
AttrDecls: Vec<Node<Annotated<AttrDecl>>> = {
<l:@L> <annotations: Annotation*> <name: Name> <required:"?"?> ":" <ty:Type> ","? <r:@R>
=>? Ok(deduplicate_annotations(AttrDecl { name, required: required.is_none(), ty}, annotations).map(|decl| vec![Node::with_source_loc(decl, Loc::new(l..r, Arc::clone(src)))])?),
<l:@L> <annotations: Annotation*> <name: Name> <required:"?"?> ":" <ty:Type> "," <r:@R> <mut ds: AttrDecls>
=>? {ds.insert(0, deduplicate_annotations(AttrDecl { name, required: required.is_none(), ty}, annotations).map(|decl| Node::with_source_loc(decl, Loc::new(l..r, Arc::clone(src))))?); Ok(ds)},
}
Comma<E>: Vec<E> = {
<e:E?> => e.into_iter().collect(),
<mut es:(<E> ",")+> <e:E> => {
es.push(e);
es
},
}
// IDENT := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']* - RESERVED
Ident: Node<Id> = {
<id: AnyIdent> =>? Id::from_str(id.node.as_ref()).map(|i| Node::with_maybe_source_loc(i, id.loc.clone())).map_err(|err : crate::parser::err::ParseErrors| ParseError::User {
error: UserError::ReservedIdentifierUsed(Node::with_maybe_source_loc(id.node.to_smolstr(), id.loc.clone()))
}),
}
// AnyIdent := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']*
AnyIdent: Node<AnyId> = {
<l:@L> NAMESPACE <r:@R>
=> Node::with_source_loc("namespace".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> ENTITY <r:@R>
=> Node::with_source_loc("entity".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> SET <r:@R>
=> Node::with_source_loc("Set".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> APPLIESTO <r:@R>
=> Node::with_source_loc("appliesTo".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> PRINCIPAL <r:@R>
=> Node::with_source_loc("principal".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> ACTION <r:@R>
=> Node::with_source_loc("action".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> RESOURCE <r:@R>
=> Node::with_source_loc("resource".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT <r:@R>
=> Node::with_source_loc("context".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> ATTRIBUTES <r:@R>
=> Node::with_source_loc("attributes".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> TAGS <r:@R>
=> Node::with_source_loc("tags".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> BOOL <r:@R>
=> Node::with_source_loc("Bool".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> LONG <r:@R>
=> Node::with_source_loc("Long".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> STRING <r:@R>
=> Node::with_source_loc("String".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> TYPE <r:@R>
=> Node::with_source_loc("type".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> IN <r:@R>
=> Node::with_source_loc("in".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> ENUM <r:@R>
=> Node::with_source_loc("enum".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
<l:@L> <i:IDENTIFIER> <r:@R>
=> Node::with_source_loc(i.parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
}
STR: Node<SmolStr> = {
<l:@L> <s:STRINGLIT> <r:@R> =>? {
#[allow(clippy::string_slice, reason = "STRINGLIT token has at least two characters, starting and ending with the ASCII character `\"`")]
to_unescaped_string(&s[1..(s.len() - 1)]).map_or_else(|e| Err(ParseError::User {
error: UserError::StringEscape(Node::with_source_loc(e, Loc::new(l..r, Arc::clone(src)))),
}), |v| Ok(Node::with_source_loc(v, Loc::new(l..r, Arc::clone(src)))))
},
}
// Name := IDENT | STR
Name: Node<SmolStr> = {
<id: Ident> => id.map(|id| id.to_smolstr()),
<s: STR> => s,
}
// QualName := Name | Path '::' STR
QualName : Node<QualName> = {
<l:@L> <p : PathInline> "::" <s:STR> <r:@R> => Node::with_source_loc(QualName::qualified(p, s.node), Loc::new(l..r, Arc::clone(src))),
<l:@L> <i : Name> <r:@R> => Node::with_source_loc(QualName::unqualified(i.node), Loc::new(l..r, Arc::clone(src))),
}
// Path := IDENT {'::' IDENT}
#[inline]
PathInline: Path = {
<l:@L> <i:Ident> <r:@R>
=> Path::single(i.node, Some(Loc::new(l..r, Arc::clone(src)))),
<l:@L> <is:(<Ident> "::")+> <i:Ident> <r:@R>
=> Path::new(i.node, is.into_iter().map(|n| n.node), Some(Loc::new(l..r, Arc::clone(src)))),
}
NonEmptyComma<E>: NonEmpty<E> = {
<e:E> => NonEmpty::singleton(e),
<es:(<E> ",")+> <e:E> => {
let mut all = NonEmpty::from_vec(es).unwrap();
all.push(e);
all
},
}
// Idents := IDENT {',' IDENT}
Idents: NonEmpty<Node<Id>> = NonEmptyComma<Ident>;
// Names := Name {',' Name}
Names: NonEmpty<Node<SmolStr>> = NonEmptyComma<Name>;
// Qualnames := Qualname {',' Qualname }
QualNames : NonEmpty<Node<QualName>> = NonEmptyComma<QualName>;
// STRs := STR {',' STR}
STRs: NonEmpty<Node<SmolStr>> = NonEmptyComma<STR>;
PrincipalOrResource: Node<PR> = {
<l:@L> PRINCIPAL <r:@R> => Node::with_source_loc(PR::Principal, Loc::new(l..r, Arc::clone(src))),
<l:@L> RESOURCE <r:@R> => Node::with_source_loc(PR::Resource, Loc::new(l..r, Arc::clone(src))),
}
// EntTypes := Path | '[' [Path {',' Path}] ']'
EntTypes: Vec<Path> = {
<et: Path>
=> vec![et],
"[" <ets: Comma<Path>> "]"
=> ets,
}
QualNameOrQualNames: NonEmpty<Node<QualName>> = {
<qn : QualName> => NonEmpty::singleton(qn),
"[" <ns: QualNames> "]" => ns,
}
Path: Path = PathInline;