// Copyright (c) ZeroC, Inc.
use crate::ast::node::Node;
use crate::grammar::*;
use crate::parsers::slice::tokens::*;
use crate::parsers::slice::grammar::*;
use crate::parsers::slice::parser::Parser;
use crate::slice_file::Span;
use crate::utils::ptr_util::{OwnedPtr, WeakPtr};
// Specify the signature of the parser's entry function.
grammar<'input, 'a>(parser: &mut Parser<'a>);
extern {
// Specify the types that the parser should use for location tracking and error emission.
type Location = crate::slice_file::Location;
type Error = crate::parsers::slice::tokens::Error;
// Link the names of terminal tokens with their actual token types. Ex: `identifier => TokenKind::Identifier`
// says that wherever we use `identifier` in the grammar, it actually represents a `TokenKind::Identifier`.
// Identifiers must match the names we use in the grammar rules, and values must match enumerators in `tokens.rs`.
enum TokenKind<'input> {
identifier => TokenKind::Identifier(<&'input str>),
string_literal => TokenKind::StringLiteral(<&'input str>),
integer_literal => TokenKind::IntegerLiteral(<&'input str>),
doc_comment => TokenKind::DocComment(<&'input str>),
// Definition keywords
module_keyword => TokenKind::ModuleKeyword,
struct_keyword => TokenKind::StructKeyword,
exception_keyword => TokenKind::ExceptionKeyword,
class_keyword => TokenKind::ClassKeyword,
interface_keyword => TokenKind::InterfaceKeyword,
enum_keyword => TokenKind::EnumKeyword,
custom_keyword => TokenKind::CustomKeyword,
type_alias_keyword => TokenKind::TypeAliasKeyword,
result_keyword => TokenKind::ResultKeyword,
// Collection keywords
sequence_keyword => TokenKind::SequenceKeyword,
dictionary_keyword => TokenKind::DictionaryKeyword,
// Primitive type keywords
bool_keyword => TokenKind::BoolKeyword,
int8_keyword => TokenKind::Int8Keyword,
uint8_keyword => TokenKind::UInt8Keyword,
int16_keyword => TokenKind::Int16Keyword,
uint16_keyword => TokenKind::UInt16Keyword,
int32_keyword => TokenKind::Int32Keyword,
uint32_keyword => TokenKind::UInt32Keyword,
varint32_keyword => TokenKind::VarInt32Keyword,
varuint32_keyword => TokenKind::VarUInt32Keyword,
int64_keyword => TokenKind::Int64Keyword,
uint64_keyword => TokenKind::UInt64Keyword,
varint62_keyword => TokenKind::VarInt62Keyword,
varuint62_keyword => TokenKind::VarUInt62Keyword,
float32_keyword => TokenKind::Float32Keyword,
float64_keyword => TokenKind::Float64Keyword,
string_keyword => TokenKind::StringKeyword,
any_class_keyword => TokenKind::AnyClassKeyword,
// Other keywords
compact_keyword => TokenKind::CompactKeyword,
idempotent_keyword => TokenKind::IdempotentKeyword,
mode_keyword => TokenKind::ModeKeyword,
stream_keyword => TokenKind::StreamKeyword,
tag_keyword => TokenKind::TagKeyword,
throws_keyword => TokenKind::ThrowsKeyword,
unchecked_keyword => TokenKind::UncheckedKeyword,
// Brackets
"(" => TokenKind::LeftParenthesis,
")" => TokenKind::RightParenthesis,
"[" => TokenKind::LeftBracket,
"]" => TokenKind::RightBracket,
"[[" => TokenKind::DoubleLeftBracket,
"]]" => TokenKind::DoubleRightBracket,
"{" => TokenKind::LeftBrace,
"}" => TokenKind::RightBrace,
"<" => TokenKind::LeftChevron,
">" => TokenKind::RightChevron,
// Symbols
"," => TokenKind::Comma,
":" => TokenKind::Colon,
"::" => TokenKind::DoubleColon,
"=" => TokenKind::Equals,
"?" => TokenKind::QuestionMark,
"->" => TokenKind::Arrow,
"-" => TokenKind::Minus,
}
}
// Grammar Rules
// TODO we can probably allow module to come before or after the compilation mode now.
pub SliceFile: (Option<FileCompilationMode>, Vec<WeakPtr<Attribute>>, Option<OwnedPtr<Module>>, Vec<Definition>) = {
<sfp: SliceFilePrelude> <m: Module?> <ds: Definition*> => (sfp.0, sfp.1, m, ds),
}
SliceFilePrelude: (Option<FileCompilationMode>, Vec<WeakPtr<Attribute>>) = {
=> (None, Vec::new()),
<sfp: SliceFilePrelude> <fe: FileCompilationMode> => handle_file_compilation_mode(parser, sfp, fe),
<mut sfp: SliceFilePrelude> <fa: FileAttribute> => {
sfp.1.push(fa);
sfp
},
}
FileCompilationMode: FileCompilationMode = {
<l: @L> mode_keyword "=" <i: Identifier> <r: @R> => {
construct_file_compilation_mode(parser, i, Span::new(l, r, parser.file_name))
},
}
Module: OwnedPtr<Module> = {
<p: Prelude> <l: @L> module_keyword <i: RelativeIdentifier> <r: @R> => {
construct_module(parser, p, i, Span::new(l, r, parser.file_name))
},
}
Definition: Definition = {
Struct => Definition::Struct(parser.ast.add_named_element(<>)),
Exception => Definition::Exception(parser.ast.add_named_element(<>)),
Class => Definition::Class(parser.ast.add_named_element(<>)),
Interface => Definition::Interface(parser.ast.add_named_element(<>)),
Enum => Definition::Enum(parser.ast.add_named_element(<>)),
CustomType => Definition::CustomType(parser.ast.add_named_element(<>)),
TypeAlias => Definition::TypeAlias(parser.ast.add_named_element(<>)),
}
Struct: OwnedPtr<Struct> = {
<p: Prelude> <l1: @L> <ck: compact_keyword?> <l2: @L> struct_keyword <i: ContainerIdentifier> <r: @R> "{" <dms: UndelimitedList<Field>> "}" ContainerEnd => {
let l = if ck.is_some() { l1 } else { l2 };
construct_struct(parser, p, ck.is_some(), i, dms, Span::new(l, r, parser.file_name))
},
}
Exception: OwnedPtr<Exception> = {
<p: Prelude> <l: @L> exception_keyword <i: ContainerIdentifier> <r: @R> <tr: (":" <TypeRef>)?> "{" <dms: UndelimitedList<Field>> "}" ContainerEnd => {
construct_exception(parser, p, i, tr, dms, Span::new(l, r, parser.file_name))
},
}
Class: OwnedPtr<Class> = {
<p: Prelude> <l: @L> class_keyword <i: ContainerIdentifier> <r1: @R> <ci: CompactId?> <r2: @R> <tr: (":" <TypeRef>)?> "{" <dms: UndelimitedList<Field>> "}" ContainerEnd => {
let r = if ci.is_some() { r2 } else { r1 };
construct_class(parser, p, i, ci, tr, dms, Span::new(l, r, parser.file_name))
},
}
Field: OwnedPtr<Field> = {
<p: Prelude> <l1: @L> <t: Tag?> <l2: @L> <i: Identifier> ":" <tr: TypeRef> <r: @R> => {
let l = if t.is_some() { l1 } else { l2 };
construct_field(parser, p, i, t, tr, Span::new(l, r, parser.file_name))
},
}
Interface: OwnedPtr<Interface> = {
<p: Prelude> <l: @L> interface_keyword <i: ContainerIdentifier> <r: @R> <trs: (":" <NonEmptyCommaList<TypeRef>>)?> "{" <os: Operation*> "}" ContainerEnd => {
construct_interface(parser, p, i, trs, os, Span::new(l, r, parser.file_name))
},
}
Operation: OwnedPtr<Operation> = {
<p: Prelude> <l1: @L> <ik: idempotent_keyword?> <l2: @L> <i: ContainerIdentifier> "(" <ps: UndelimitedList<Parameter>> ")" <rt: ReturnType?> <r1: @R> <es: ExceptionSpecification?> <r2: @R> ContainerEnd => {
let l = if ik.is_some() { l1 } else { l2 };
let r = if es.is_some() { r2 } else { r1 };
construct_operation(parser, p, ik.is_some(), i, ps, rt, es, Span::new(l, r, parser.file_name))
},
}
Parameter: OwnedPtr<Parameter> = {
<p: Prelude> <l1: @L> <t: Tag?> <l2: @L> <i: Identifier> ":" <s: stream_keyword?> <tr: TypeRef> <r: @R> => {
let l = if t.is_some() { l1 } else { l2 };
construct_parameter(parser, p, i, t, s.is_some(), tr, Span::new(l, r, parser.file_name))
},
}
ReturnType: Vec<OwnedPtr<Parameter>> = {
"->" <l: @L> <t: Tag?> <s: stream_keyword?> <tr: TypeRef> <r: @R> => {
construct_single_return_type(parser, t, s.is_some(), tr, Span::new(l, r, parser.file_name))
},
"->" <l: @L> "(" <ps: UndelimitedList<Parameter>> ")" <r: @R> => {
check_return_tuple(parser, &ps, Span::new(l, r, parser.file_name));
ps
},
}
ExceptionSpecification: Vec<TypeRef> = {
throws_keyword <TypeRef> => vec![<>],
throws_keyword "(" <NonEmptyCommaList<TypeRef>> ")" => <>,
}
Enum: OwnedPtr<Enum> = {
<p: Prelude> <l1: @L> <ck: compact_keyword?> <uk: unchecked_keyword?> <l2: @L> enum_keyword <i: ContainerIdentifier> <r: @R> <tr: (":" <TypeRef>)?> "{" <es: UndelimitedList<Enumerator>> "}" ContainerEnd => {
let l = if ck.is_some() || uk.is_some() { l1 } else { l2 };
construct_enum(parser, p, ck.is_some(), uk.is_some(), i, tr, es, Span::new(l, r, parser.file_name))
},
}
Enumerator: OwnedPtr<Enumerator> = {
<p: Prelude> <l: @L> <i: ContainerIdentifier> <afs: ("(" <UndelimitedList<Field>> ")")?> <si: ("=" <SignedInteger>)?> <r: @R> ContainerEnd => {
construct_enumerator(parser, p, i, afs, si, Span::new(l, r, parser.file_name))
},
}
CustomType: OwnedPtr<CustomType> = {
<p: Prelude> <l: @L> custom_keyword <i: Identifier> <r: @R> => {
construct_custom_type(parser, p, i, Span::new(l, r, parser.file_name))
},
}
TypeAlias: OwnedPtr<TypeAlias> = {
<p: Prelude> <l: @L> type_alias_keyword <i: Identifier> <r: @R> "=" <tr: TypeRef> => {
construct_type_alias(parser, p, i, tr, Span::new(l, r, parser.file_name))
},
}
Result: OwnedPtr<ResultType> = {
result_keyword "<" <success_type: TypeRef> "," <failure_type: TypeRef> ">" => {
OwnedPtr::new(ResultType { success_type, failure_type })
},
}
Sequence: OwnedPtr<Sequence> = {
sequence_keyword "<" <element_type: TypeRef> ">" => {
OwnedPtr::new(Sequence { element_type })
},
}
Dictionary: OwnedPtr<Dictionary> = {
dictionary_keyword "<" <key_type: TypeRef> "," <value_type: TypeRef> ">" => {
OwnedPtr::new(Dictionary { key_type, value_type })
},
}
Primitive: Primitive = {
bool_keyword => Primitive::Bool,
int8_keyword => Primitive::Int8,
uint8_keyword => Primitive::UInt8,
int16_keyword => Primitive::Int16,
uint16_keyword => Primitive::UInt16,
int32_keyword => Primitive::Int32,
uint32_keyword => Primitive::UInt32,
varint32_keyword => Primitive::VarInt32,
varuint32_keyword => Primitive::VarUInt32,
int64_keyword => Primitive::Int64,
uint64_keyword => Primitive::UInt64,
varint62_keyword => Primitive::VarInt62,
varuint62_keyword => Primitive::VarUInt62,
float32_keyword => Primitive::Float32,
float64_keyword => Primitive::Float64,
string_keyword => Primitive::String,
any_class_keyword => Primitive::AnyClass,
}
TypeRef: TypeRef = {
<l: @L> <las: LocalAttribute*> <trd: TypeRefDefinition> <o: "?"?> <r: @R> => {
construct_type_ref(parser, las, trd, o.is_some(), Span::new(l, r, parser.file_name))
},
}
TypeRefDefinition: TypeRefDefinition = {
Primitive => primitive_to_type_ref_definition(parser, <>),
Result => anonymous_type_to_type_ref_definition(parser, <>),
Sequence => anonymous_type_to_type_ref_definition(parser, <>),
Dictionary => anonymous_type_to_type_ref_definition(parser, <>),
RelativeIdentifier => construct_unpatched_type_ref_definition(<>),
GlobalIdentifier => construct_unpatched_type_ref_definition(<>),
}
FileAttribute = "[[" <Attribute> "]]";
LocalAttribute = "[" <Attribute> "]";
Attribute: WeakPtr<Attribute> = {
<l: @L> <rsi: RelativeIdentifier> <aas: ("(" <CommaList<AttributeArgument>> ")")?> <r: @R> => {
construct_attribute(parser, rsi, aas, Span::new(l, r, parser.file_name))
},
}
AttributeArgument: String = {
<sl: string_literal> => unescape_string_literal(sl),
<i: identifier> => i.to_owned(),
}
Identifier: Identifier = {
<l: @L> <i: identifier> <r: @R> => {
Identifier { value: i.to_owned(), span: Span::new(l, r, parser.file_name) }
},
}
RelativeIdentifier: Identifier = {
<l: @L> <i: identifier> <mut v: ("::" <identifier>)*> <r: @R> => {
v.insert(0, i);
Identifier { value: v.join("::"), span: Span::new(l, r, parser.file_name) }
},
}
GlobalIdentifier: Identifier = {
<l: @L> <mut v: ("::" <identifier>)+> <r: @R> => {
v.insert(0, ""); // Gives a leading "::" when we `join`.
Identifier { value: v.join("::"), span: Span::new(l, r, parser.file_name) }
},
}
Integer: Integer<i128> = {
<l: @L> <i: integer_literal> <r: @R> => {
try_parse_integer(parser, i, Span::new(l, r, parser.file_name))
},
}
SignedInteger: Integer<i128> = {
<i: Integer> => i,
<l: @L> "-" <mut i: Integer> => Integer {
value: -i.value,
span: Span { start: l, ..i.span },
},
}
Tag: Integer<u32> = {
tag_keyword "(" <i: SignedInteger> ")" => {
parse_tag_value(parser, i)
},
}
CompactId: Integer<u32> = {
"(" <i: SignedInteger> ")" => {
parse_compact_id_value(parser, i)
},
}
Prelude: (Vec<(&'input str, Span)>, Vec<WeakPtr<Attribute>>) = {
=> (Vec::new(), Vec::new()),
<mut prelude: Prelude> <l: @L> <comment: doc_comment> <r: @R> => {
prelude.0.push((comment, Span::new(l, r, parser.file_name)));
prelude
},
<mut prelude: Prelude> <attribute: LocalAttribute> => {
prelude.1.push(attribute);
prelude
},
}
// Utility Rules
// A comma separated list of 1 or more elements, with an optional trailing comma.
NonEmptyCommaList<T>: Vec<T> = {
<element: T> <mut vector: ("," <T>)*> ","? => {
vector.insert(0, element);
vector
},
}
// A comma separated list of 0 or more elements, with an optional trailing comma.
CommaList<T>: Vec<T> = {
NonEmptyCommaList<T> => <>,
=> Vec::new(),
}
// A list of 0 or more elements with no required separators.
// A single comma can optionally be placed after each element (including a trailing comma),
// but these are ignored by the compiler and only for user-readability.
UndelimitedList<T>: Vec<T> = {
(<T> ","?)* => <>,
}
ContainerIdentifier: Identifier = {
Identifier => {
parser.current_scope.push_scope(&<>.value);
<>
},
}
ContainerEnd: () = {
=> parser.current_scope.pop_scope(),
}