//
// 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::cedar_schema::err::{RawErrorRecovery, RawUserError, UserError};
use cedar_policy_core::parser::{Node, Loc, unescape::to_unescaped_string, cst::Ref};
use cedar_policy_core::ast::Id;
use smol_str::SmolStr;
use smol_str::ToSmolStr;
use crate::cedar_schema::ast::{
Path,
EntityDecl,
Declaration,
Namespace,
Schema as ASchema,
Type as SType,
AttrDecl,
ActionDecl,
PR,
AppDecl,
TypeDecl,
PrimitiveType,
QualName,
PRAppDecl};
use nonempty::{NonEmpty, nonempty};
use itertools::Either;
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>);
extern {
type Error = RawUserError;
}
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,
// 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
",", ";", ":", "::", "{", "}", "[", "]",
"<", ">", "=", "?",
}
// Schema := {Namespace}
pub Schema: ASchema = {
<ns: Namespace*> => ns,
}
// Namespace := 'namespace' Path '{' {Decl} '}'
Namespace: Node<Namespace> = {
<l:@L> NAMESPACE <p: Path> "{" <decls: Decl*> "}" <r:@R>
=> Node::with_source_loc(Namespace { name: Some(Node::with_source_loc(p, Loc::new(l..r, Arc::clone(src)))), decls}, Loc::new(l..r, Arc::clone(src))),
<l:@L> <decl: Decl> <r:@R> => Node::with_source_loc(Namespace {name: None, decls: vec![decl]}, Loc::new(l..r, Arc::clone(src))),
}
// Decl := Entity | Action | TypeDecl
Decl: Node<Declaration> = {
<e:Entity> => e,
<a:Action> => a,
<t:TypeDecl> => t,
}
// Entity := 'entity' Idents ['in' EntOrTypes] [['='] RecType] ';'
Entity: Node<Declaration> = {
<l:@L> ENTITY <ets: Idents> <ps:(IN <EntTypes>)?> <ds:("="? "{" <AttrDecls?> "}")?> <ts:(TAGS <Type>)?> ";" <r:@R>
=> Node::with_source_loc(Declaration::Entity(EntityDecl { names: ets, member_of_types: ps.unwrap_or_default(), attrs: ds.map(|ds| ds.unwrap_or_default()).unwrap_or_default(), tags: ts }), 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') ':' EntOrTyps [',' | ',' AppDecls]
// | 'context' ':' (Path | RecType) [',' | ',' AppDecls]
AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
<l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> ","? <r:@R>
=>?
NonEmpty::collect(ets.into_iter()).ok_or(ParseError::User {
error: Node::with_source_loc(UserError::EmptyList, Loc::new(l..r, Arc::clone(src)))})
.map(|ets|
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys: ets}), 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>
=>?
NonEmpty::collect(ets.into_iter()).ok_or(ParseError::User {
error: Node::with_source_loc(UserError::EmptyList, Loc::new(l..r, Arc::clone(src)))})
.map(|ets|
{
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys: ets}), 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)))
},
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" ","? <r:@R>
=>
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
},
}
// SetType := 'Set' '<' Type '>'
// RecType := '{' [AttrDecls] '}'
// Type := PRIMTYPE | Path | SetType | RecType
pub Type: Node<SType> = {
<p:Path>
=> { let loc = p.loc().clone(); Node::with_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 := Name ['?'] ':' Type [',' | ',' AttrDecls]
AttrDecls: Vec<Node<AttrDecl>> = {
<l:@L> <name: Name> <required:"?"?> ":" <ty:Type> ","? <r:@R>
=> vec![Node::with_source_loc(AttrDecl { name, required: required.is_none(), ty}, Loc::new(l..r, Arc::clone(src)))],
<l:@L> <name: Name> <required:"?"?> ":" <ty:Type> "," <r:@R> <mut ds: AttrDecls>
=> {ds.insert(0, Node::with_source_loc(AttrDecl { name, required: required.is_none(), ty}, Loc::new(l..r, Arc::clone(src)))); 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']*
Ident: Node<Id> = {
<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>
=>? Err(ParseError::User {
error: Node::with_source_loc(UserError::ReservedIdentifierUsed("in".into()), Loc::new(l..r, Arc::clone(src)))
}),
<l:@L> <i:IDENTIFIER> <r:@R>
=>? Id::from_str(i)
.map(|id : Id| Node::with_source_loc(id, Loc::new(l..r, Arc::clone(src))))
.map_err(|err : cedar_policy_core::parser::err::ParseErrors|
ParseError::User {
error: Node::with_source_loc(UserError::ReservedIdentifierUsed(i.to_smolstr()), Loc::new(l..r, Arc::clone(src)))
}
)
}
STR: Node<SmolStr> = {
<l:@L> <s:STRINGLIT> <r:@R>
=>? to_unescaped_string(&s[1..(s.len() - 1)]).map_or_else(|e| Err(ParseError::User {
error: Node::with_source_loc(UserError::StringEscape(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, 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), Loc::new(l..r, Arc::clone(src))),
}
// Idents := IDENT {',' IDENT}
Idents: Vec<Node<Id>> = {
<i:Ident> => vec![i],
<mut is:(<Ident> ",")+> <i:Ident> => {
is.push(i);
is
},
}
// Names := Name {',' Name}
Names: NonEmpty<Node<SmolStr>> = {
<n:Name> => NonEmpty::new(n),
<mut ns:(<Name> ",")+> <n:Name> => {
let mut all = NonEmpty::new(n);
all.append(&mut ns);
all
},
}
// Qualnames := Qualname {',' Qualname }
QualNames : NonEmpty<Node<QualName>> = {
<n:QualName> => NonEmpty::singleton(n),
<ns:(<QualName> ",")+> <n:QualName> => {
NonEmpty {
head : n,
tail : ns
}
},
}
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))),
}
// EntType := Path
// EntTypes := Path {',' Path}
// EntOrTyps := EntType | '[' [EntTypes] ']'
EntTypes: Vec<Path> = {
<et: Path>
=> vec![et],
"[" <ets: Comma<Path>> "]"
=> ets,
}
NameOrNames: Vec<Node<SmolStr>> = {
<n : Name> => vec![n],
"[" <ns:Comma<Name>> "]" => ns,
}
QualNameOrQualNames: NonEmpty<Node<QualName>> = {
<qn : QualName> => NonEmpty::singleton(qn),
"[" <ns: QualNames> "]" => ns,
}
Path: Path = PathInline;