use std::collections::HashSet;
use thiserror::Error;
use crate::{
grammar::ProgramParser,
lexer::{Lexer, LexicalError},
};
pub mod visitor;
#[derive(Error, Debug, PartialEq)]
pub enum ParseError {
#[error(transparent)]
Lexical(#[from] LexicalError),
#[error("duplicate type `{0}`")]
DuplicateType(String),
#[error("duplicate ctor `{0}`")]
DuplicateCtor(String),
#[error("duplicate service `{0}`")]
DuplicateService(String),
#[error("duplicate service method `{method}` in service `{service}`")]
DuplicateServiceMethod { method: String, service: String },
#[error("duplicate variant `{0}`")]
DuplicateEnumVariant(String),
#[error("duplicate field `{0}`")]
DuplicateStructField(String),
#[error("struct has mixed named and unnamed fields")]
StructMixedFields,
#[error("parse error: `{0}`")]
Other(String),
}
pub fn parse_idl(idl: &str) -> Result<Program, ParseError> {
let lexer = Lexer::new(idl);
let parser = ProgramParser::new();
let program = parser.parse(lexer).map_err(|e| match e {
lalrpop_util::ParseError::User { error } => error,
_ => ParseError::Other(e.to_string()),
})?;
Ok(program)
}
type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, PartialEq, Clone)]
pub struct Program {
ctor: Option<Ctor>,
services: Vec<Service>,
types: Vec<Type>,
}
impl Program {
pub(crate) fn new(
ctor: Option<Ctor>,
services: Vec<Service>,
types: Vec<Type>,
) -> ParseResult<Self> {
let mut seen_types = HashSet::new();
for t in &types {
if !seen_types.insert(t.name().to_lowercase()) {
return Err(ParseError::DuplicateType(t.name().to_string()));
}
}
let mut seen_services = HashSet::new();
for s in &services {
if !seen_services.insert(s.name().to_lowercase()) {
return Err(ParseError::DuplicateService(s.name().to_string()));
}
}
Ok(Self {
ctor,
services,
types,
})
}
pub fn ctor(&self) -> Option<&Ctor> {
self.ctor.as_ref()
}
pub fn services(&self) -> &[Service] {
&self.services
}
pub fn types(&self) -> &[Type] {
&self.types
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Ctor {
funcs: Vec<CtorFunc>,
}
impl Ctor {
pub(crate) fn new(funcs: Vec<CtorFunc>) -> ParseResult<Self> {
let mut seen = HashSet::new();
for f in &funcs {
if !seen.insert(f.name().to_lowercase()) {
return Err(ParseError::DuplicateCtor(f.name().to_string()));
}
}
Ok(Self { funcs })
}
pub fn funcs(&self) -> &[CtorFunc] {
&self.funcs
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct CtorFunc {
name: String,
params: Vec<FuncParam>,
}
impl CtorFunc {
pub(crate) fn new(name: String, params: Vec<FuncParam>) -> Self {
Self { name, params }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn params(&self) -> &[FuncParam] {
&self.params
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Service {
name: String,
funcs: Vec<ServiceFunc>,
events: Vec<ServiceEvent>,
}
impl Service {
pub(crate) fn new(
name: String,
funcs: Vec<ServiceFunc>,
events: Vec<ServiceEvent>,
) -> ParseResult<Self> {
let mut seen = HashSet::new();
for f in &funcs {
if !seen.insert(f.name().to_lowercase()) {
return Err(ParseError::DuplicateServiceMethod {
method: f.name().to_string(),
service: name.to_string(),
});
}
}
Ok(Self {
name,
funcs,
events,
})
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn funcs(&self) -> &[ServiceFunc] {
&self.funcs
}
pub fn events(&self) -> &[ServiceEvent] {
&self.events
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ServiceFunc {
name: String,
params: Vec<FuncParam>,
output: TypeDecl,
is_query: bool,
}
impl ServiceFunc {
pub(crate) fn new(
name: String,
params: Vec<FuncParam>,
output: TypeDecl,
is_query: bool,
) -> Self {
Self {
name,
params,
output,
is_query,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn params(&self) -> &[FuncParam] {
&self.params
}
pub fn output(&self) -> &TypeDecl {
&self.output
}
pub fn is_query(&self) -> bool {
self.is_query
}
}
pub type ServiceEvent = EnumVariant;
#[derive(Debug, PartialEq, Clone)]
pub struct FuncParam {
name: String,
type_decl: TypeDecl,
}
impl FuncParam {
pub(crate) fn new(name: String, type_decl: TypeDecl) -> Self {
Self { name, type_decl }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn type_decl(&self) -> &TypeDecl {
&self.type_decl
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Type {
name: String,
def: TypeDef,
}
impl Type {
pub(crate) fn new(name: String, def: TypeDef) -> Self {
Self { name, def }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn def(&self) -> &TypeDef {
&self.def
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum TypeDecl {
Vector(Box<TypeDecl>),
Array {
item: Box<TypeDecl>,
len: u32,
},
Map {
key: Box<TypeDecl>,
value: Box<TypeDecl>,
},
Optional(Box<TypeDecl>),
Result {
ok: Box<TypeDecl>,
err: Box<TypeDecl>,
},
Id(TypeId),
Def(TypeDef),
}
#[derive(Debug, PartialEq, Clone)]
pub enum TypeId {
Primitive(PrimitiveType),
UserDefined(String),
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[repr(u8)]
pub enum PrimitiveType {
Null,
Bool,
Char,
Str,
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
ActorId,
CodeId,
MessageId,
H256,
U256,
H160,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroU256,
}
impl PrimitiveType {
pub(crate) fn str_to_enum(str: &str) -> Option<Self> {
match str {
"bool" => Some(PrimitiveType::Bool),
"char" => Some(PrimitiveType::Char),
"str" => Some(PrimitiveType::Str),
"u8" => Some(PrimitiveType::U8),
"u16" => Some(PrimitiveType::U16),
"u32" => Some(PrimitiveType::U32),
"u64" => Some(PrimitiveType::U64),
"u128" => Some(PrimitiveType::U128),
"i8" => Some(PrimitiveType::I8),
"i16" => Some(PrimitiveType::I16),
"i32" => Some(PrimitiveType::I32),
"i64" => Some(PrimitiveType::I64),
"i128" => Some(PrimitiveType::I128),
"h160" => Some(PrimitiveType::H160),
"h256" => Some(PrimitiveType::H256),
"u256" => Some(PrimitiveType::U256),
"nat8" => Some(PrimitiveType::NonZeroU8),
"nat16" => Some(PrimitiveType::NonZeroU16),
"nat32" => Some(PrimitiveType::NonZeroU32),
"nat64" => Some(PrimitiveType::NonZeroU64),
"nat128" => Some(PrimitiveType::NonZeroU128),
"nat256" => Some(PrimitiveType::NonZeroU256),
"actor_id" => Some(PrimitiveType::ActorId),
"code_id" => Some(PrimitiveType::CodeId),
"message_id" => Some(PrimitiveType::MessageId),
_ => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum TypeDef {
Struct(StructDef),
Enum(EnumDef),
}
#[derive(Debug, PartialEq, Clone)]
pub struct StructDef {
fields: Vec<StructField>,
}
impl StructDef {
pub(crate) fn new(fields: Vec<StructField>) -> ParseResult<Self> {
let all_unnamed = fields.iter().all(|f| f.name().is_none());
let all_named = fields.iter().all(|f| f.name().is_some());
if !all_unnamed && !all_named {
return Err(ParseError::StructMixedFields);
}
let mut seen = HashSet::new();
if all_named {
for f in &fields {
let name = f.name().unwrap();
if !seen.insert(name.to_lowercase()) {
return Err(ParseError::DuplicateStructField(name.to_string()));
}
}
}
Ok(Self { fields })
}
pub fn fields(&self) -> &[StructField] {
&self.fields
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct StructField {
name: Option<String>,
type_decl: TypeDecl,
}
impl StructField {
pub(crate) fn new(name: Option<String>, type_decl: TypeDecl) -> Self {
Self { name, type_decl }
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn type_decl(&self) -> &TypeDecl {
&self.type_decl
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct EnumDef {
variants: Vec<EnumVariant>,
}
impl EnumDef {
pub(crate) fn new(variants: Vec<EnumVariant>) -> ParseResult<Self> {
let mut seen = HashSet::new();
for v in &variants {
if !seen.insert(v.name().to_lowercase()) {
return Err(ParseError::DuplicateEnumVariant(v.name().to_string()));
}
}
Ok(Self { variants })
}
pub fn variants(&self) -> &[EnumVariant] {
&self.variants
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct EnumVariant {
name: String,
type_decl: Option<TypeDecl>,
}
impl EnumVariant {
pub(crate) fn new(name: String, type_decl: Option<TypeDecl>) -> Self {
Self { name, type_decl }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn type_decl(&self) -> Option<&TypeDecl> {
self.type_decl.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parser_works() {
let program_idl = r"
type ThisThatSvcAppTupleStruct = struct {
bool,
};
type ThisThatSvcAppDoThatParam = struct {
p1: u32,
p2: str,
p3: ThisThatSvcAppManyVariants,
};
type ThisThatSvcAppManyVariants = enum {
One,
Two: u32,
Three: opt u256,
Four: struct { a: u32, b: opt u16 },
Five: struct { str, h256 },
Six: struct { u32 },
Seven: [map (u32, str), 10],
Eight: actor_id,
};
constructor {
New : (p1: u32);
};
service {
DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: ThisThatSvcAppTupleStruct) -> struct { str, u32 };
DoThat : (param: ThisThatSvcAppDoThatParam) -> result (struct { str, u32 }, struct { str });
query This : (v1: vec u16) -> u32;
query That : (v1: null) -> result (str, str);
events {
ThisDone;
ThatDone: u32;
SomethingHappened: struct { str, u32 };
SomethingDone: ThisThatSvcAppManyVariants;
}
};
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.types().len(), 3);
assert_eq!(program.ctor().unwrap().funcs().len(), 1);
assert_eq!(program.services().len(), 1);
assert_eq!(program.services()[0].funcs().len(), 4);
assert_eq!(program.services()[0].events().len(), 4);
}
#[test]
fn parser_accepts_types_service() {
let program_idl = r"
type T = enum { One };
service {}
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.types().len(), 1);
assert_eq!(program.services().len(), 1);
assert_eq!(program.services()[0].funcs().len(), 0);
}
#[test]
fn parser_accepts_ctor_service() {
let program_idl = r"
constructor {};
service {}
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.ctor().unwrap().funcs().len(), 0);
assert_eq!(program.services().len(), 1);
assert_eq!(program.services()[0].funcs().len(), 0);
}
#[test]
fn parser_accepts_multiple_services() {
let program_idl = r"
service {};
service SomeService {};
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.services().len(), 2);
assert_eq!(program.services()[0].name(), "");
assert_eq!(program.services()[1].name(), "SomeService");
}
#[test]
fn parser_accepts_types_ctor_service() {
let program_idl = r"
type T = enum { One };
constructor {};
service {}
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.types().len(), 1);
assert_eq!(program.ctor().unwrap().funcs().len(), 0);
assert_eq!(program.services().len(), 1);
assert_eq!(program.services()[0].funcs().len(), 0);
}
#[test]
fn parser_requires_semicolon_between_types_and_service() {
let program_idl = r"
type T = enum { One }
service {}
";
let program = parse_idl(program_idl);
assert!(program.is_err());
}
#[test]
fn parser_recognizes_builtin_types_as_primitives() {
let program_idl = r"
service {
DoThis : (p1: actor_id, p2: code_id, p3: message_id, p4: h256, p5: u256, p6: h160) -> null;
}
";
let program = parse_idl(program_idl).unwrap();
assert_eq!(program.services().len(), 1);
program.services()[0]
.funcs()
.first()
.unwrap()
.params()
.iter()
.for_each(|p| match p.type_decl() {
TypeDecl::Id(TypeId::Primitive(PrimitiveType::ActorId)) => {
assert_eq!(p.name(), "p1");
}
TypeDecl::Id(TypeId::Primitive(PrimitiveType::CodeId)) => {
assert_eq!(p.name(), "p2");
}
TypeDecl::Id(TypeId::Primitive(PrimitiveType::MessageId)) => {
assert_eq!(p.name(), "p3");
}
TypeDecl::Id(TypeId::Primitive(PrimitiveType::H256)) => {
assert_eq!(p.name(), "p4");
}
TypeDecl::Id(TypeId::Primitive(PrimitiveType::U256)) => {
assert_eq!(p.name(), "p5");
}
TypeDecl::Id(TypeId::Primitive(PrimitiveType::H160)) => {
assert_eq!(p.name(), "p6");
}
_ => panic!("unexpected type"),
});
}
#[test]
fn parser_rejects_duplicate_names() {
let program_idl = r"
type A = enum { One };
type A = enum { Two };
service {};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateType("A".to_owned()));
}
#[test]
fn parser_rejects_duplicate_unnamed_services() {
let program_idl = r"
service {};
service {};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateService("".to_owned()));
}
#[test]
fn parser_rejects_duplicate_named_services() {
let program_idl = r"
service A {};
service B {};
service A {};
service {};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateService("A".to_owned()));
}
#[test]
fn parser_rejects_duplicate_service_methods() {
let program_idl = r"
service {
DoTHIS : () -> null;
DoThis : () -> null;
};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(
err,
ParseError::DuplicateServiceMethod {
method: "DoThis".to_owned(),
service: "".to_owned()
}
);
}
#[test]
fn parser_rejects_duplicate_ctor_funcs() {
let program_idl = r"
constructor {
New : ();
new : ();
};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateCtor("new".to_owned()));
}
#[test]
fn parser_rejects_duplicate_enum_variants() {
let program_idl = r"
type T = enum { One, One };
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateEnumVariant("One".to_owned()));
}
#[test]
fn parser_rejects_duplicate_struct_fields() {
let program_idl = r"
type T = struct {
a: u32,
a: u32,
};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::DuplicateStructField("a".to_owned()));
}
#[test]
fn parser_rejects_mixed_named_unnamed_struct_fields() {
let program_idl = r"
type T = struct {
a: u32,
u32,
};
";
let err = parse_idl(program_idl).unwrap_err();
assert_eq!(err, ParseError::StructMixedFields);
}
#[test]
fn parser_accepts_struct_field_reserved_keywords() {
const IDL: &str = r#"
type MyStruct = struct {
query: u8,
result: u8,
};
"#;
let expected = TypeDef::Struct(
StructDef::new(vec![
StructField::new(
Some("query".to_owned()),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::U8)),
),
StructField::new(
Some("result".to_owned()),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::U8)),
),
])
.unwrap(),
);
let program = parse_idl(IDL).unwrap();
let my_struct = program
.types()
.iter()
.find(|t| t.name() == "MyStruct")
.unwrap();
assert_eq!(&expected, my_struct.def());
}
#[test]
fn parser_accepts_func_param_reserved_keywords() {
const IDL: &str = r#"
service {
DoThis : (constructor: u8, service: u8, events: vec u8) -> null;
}
"#;
let expected = Service::new(
"".to_owned(),
vec![ServiceFunc::new(
"DoThis".to_owned(),
vec![
FuncParam::new(
"constructor".to_owned(),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::U8)),
),
FuncParam::new(
"service".to_owned(),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::U8)),
),
FuncParam::new(
"events".to_owned(),
TypeDecl::Vector(Box::new(TypeDecl::Id(TypeId::Primitive(
PrimitiveType::U8,
)))),
),
],
TypeDecl::Id(TypeId::Primitive(PrimitiveType::Null)),
false,
)],
vec![],
)
.unwrap();
let program = parse_idl(IDL).unwrap();
let my_service = program.services().iter().find(|t| t.name() == "").unwrap();
assert_eq!(&expected, my_service);
}
#[test]
fn parser_accepts_nonzero_primitives() {
const IDL: &str = r#"
type MyStruct = struct {
query: nat32,
data: nat256,
result: nat8
};
"#;
let expected = TypeDef::Struct(
StructDef::new(vec![
StructField::new(
Some("query".to_owned()),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::NonZeroU32)),
),
StructField::new(
Some("data".to_owned()),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::NonZeroU256)),
),
StructField::new(
Some("result".to_owned()),
TypeDecl::Id(TypeId::Primitive(PrimitiveType::NonZeroU8)),
),
])
.unwrap(),
);
let program = parse_idl(IDL).unwrap();
let my_struct = program
.types()
.iter()
.find(|t| t.name() == "MyStruct")
.unwrap();
assert_eq!(&expected, my_struct.def());
}
}