candid_parser 0.3.1

Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer. This crate contains the parser and the binding generator for Candid.
Documentation
use super::test::{Assert, Input, Test};
use super::token::{Token, error2, LexicalError, Span, TriviaMap};
use candid::{Principal, types::Label};
use crate::syntax::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLActorType};
use candid::types::value::{IDLField, IDLValue, IDLArgs, VariantValue};
use candid::types::{TypeEnv, FuncMode};
use candid::utils::check_unique;

grammar(trivia: Option<&TriviaMap>);

extern {
    type Location = usize;
    type Error = LexicalError;
    enum Token {
        "decimal" => Token::Decimal(<String>),
        "hex" => Token::Hex(<String>),
        "float" => Token::Float(<String>),
        "bool" => Token::Boolean(<bool>),
        "text" => Token::Text(<String>),
        "id" => Token::Id(<String>),
        "null" => Token::Null,
        "opt" => Token::Opt,
        "vec" => Token::Vec,
        "record" => Token::Record,
        "variant" => Token::Variant,
        "func" => Token::Func,
        "service" => Token::Service,
        "oneway" => Token::Oneway,
        "query" => Token::Query,
        "composite_query" => Token::CompositeQuery,
        "blob" => Token::Blob,
        "type" => Token::Type,
        "import" => Token::Import,
        "principal" => Token::Principal,
        "sign" => Token::Sign(<char>),
        "=" => Token::Equals,
        "==" => Token::TestEqual,
        "!=" => Token::NotEqual,
        "!:" => Token::NotDecode,
        "(" => Token::LParen,
        ")" => Token::RParen,
        "{" => Token::LBrace,
        "}" => Token::RBrace,
        "," => Token::Comma,
        "." => Token::Dot,
        ";" => Token::Semi,
        ":" => Token::Colon,
        "->" => Token::Arrow,
    }
}


// Value
pub Args: IDLArgs = "(" <SepBy<AnnVal, ",">> ")" => IDLArgs { args: <> };

pub Arg: IDLValue = {
    "bool" => IDLValue::Bool(<>),
    NumLiteral => <>,
    Text => IDLValue::Text(<>),
    Bytes => IDLValue::Blob(<>),
    "null" => IDLValue::Null,
    "opt" <Arg> => IDLValue::Opt(Box::new(<>)),
    "vec" "{" <SepBy<AnnVal, ";">> "}" => IDLValue::Vec(<>),
    "record" "{" <Sp<SepBy<RecordField, ";">>> "}" =>? {
        let mut id: u32 = 0;
        let span = <>.1.clone();
        let mut fs: Vec<IDLField> = <>.0.into_iter().map(|f| {
          match f.id {
            Label::Unnamed(_) => {
              id = id + 1;
              IDLField { id: Label::Unnamed(id - 1), val: f.val }
            }
            _ => {
              id = f.id.get_id() + 1;
              f
            }
          }
        }).collect();
        fs.sort_unstable_by_key(|IDLField { id, .. }| id.get_id());
        check_unique(fs.iter().map(|f| &f.id)).map_err(|e| error2(e, span))?;
        Ok(IDLValue::Record(fs))
    },
    "variant" "{" <VariantField> "}" => IDLValue::Variant(VariantValue(Box::new(<>), 0)),
    "principal" <Sp<Text>> =>? Ok(IDLValue::Principal(Principal::from_text(&<>.0).map_err(|e| error2(e, <>.1))?)),
    "service" <Sp<Text>> =>? Ok(IDLValue::Service(Principal::from_text(&<>.0).map_err(|e| error2(e, <>.1))?)),
    "func" <id:Sp<Text>> "." <meth:Name> =>? {
      let id = Principal::from_text(&id.0).map_err(|e| error2(e, id.1))?;
      Ok(IDLValue::Func(id, meth))
    },
    "(" <AnnVal> ")" => <>,
}

Text: String = {
    Sp<"text"> =>? {
        if std::str::from_utf8(<>.0.as_bytes()).is_err() {
          Err(error2("Not valid unicode text", <>.1))
        } else {
          Ok(<>.0)
        }
    }
}

Bytes: Vec<u8> = {
    "blob" <"text"> => <>.into_bytes(),
}

Number: String = {
    "decimal" => <>,
    "hex" => num_bigint::BigInt::parse_bytes(<>.as_bytes(), 16).unwrap().to_str_radix(10),
}

AnnVal: IDLValue = {
    <Arg> => <>,
    <arg:Sp<Arg>> ":" <typ:Sp<Typ>> =>? {
        let env = TypeEnv::new();
        let typ = crate::typing::ast_to_type(&env, &typ.0).map_err(|e| error2(e, typ.1))?;
        arg.0.annotate_type(true, &env, &typ).map_err(|e| error2(e, arg.1))
    }
}

NumLiteral: IDLValue = {
    <sign:"sign"?> <n:Number> => {
        let num = match sign {
            Some('-') => format!("-{}", n),
            _ => n,
        };
        IDLValue::Number(num)
    },
    <sign:"sign"?> <n:Sp<"float">> =>? {
        let span = n.1.clone();
        let num = match sign {
            Some('-') => format!("-{}", n.0),
            _ => n.0,
        };
        let f = num.parse::<f64>().map_err(|_| error2("not a float", span))?;
        Ok(IDLValue::Float64(f))
    },
}

FieldId: u32 = {
    Sp<"decimal"> =>? <>.0.parse::<u32>().map_err(|_| error2("field id out of u32 range", <>.1)),
    Sp<"hex"> =>? u32::from_str_radix(&<>.0, 16).map_err(|_| error2("field id out of u32 range", <>.1)),
}

Field: IDLField = {
    <n:FieldId> "=" <v:AnnVal> =>? Ok(IDLField { id: Label::Id(n), val: v }),
    <n:Name> "=" <v:AnnVal> => IDLField { id: Label::Named(n), val: v },
}

VariantField: IDLField = {
    Field => <>,
    Name => IDLField { id: Label::Named(<>), val: IDLValue::Null },
    FieldId =>? Ok(IDLField { id: Label::Id(<>), val: IDLValue::Null }),
}

RecordField: IDLField = {
    Field => <>,
    AnnVal => IDLField { id: Label::Unnamed(0), val:<> },
}

// Type
pub Typs: IDLTypes = TupTyp => IDLTypes { args:<> };

pub Typ: IDLType = {
    PrimTyp => <>,
    "opt" <Typ> => IDLType::OptT(Box::new(<>)),
    "vec" <Typ> => IDLType::VecT(Box::new(<>)),
    "blob" => IDLType::VecT(Box::new(IDLType::PrimT(PrimType::Nat8))),
    "record" "{" <Sp<SepBy<RecordFieldTyp, ";">>> "}" =>? {
        let mut id: u32 = 0;
        let span = <>.1.clone();
        let mut fs: Vec<TypeField> = <>.0.iter().map(|f| {
          let label = match f.label {
              Label::Unnamed(_) => { id = id + 1; Label::Unnamed(id - 1) },
              ref l => { id = l.get_id() + 1; l.clone() },
          };
          TypeField { label, typ: f.typ.clone(), docs: f.docs.clone() }
        }).collect();
        fs.sort_unstable_by_key(|TypeField { label, .. }| label.get_id());
        check_unique(fs.iter().map(|f| &f.label)).map_err(|e| error2(e, span))?;
        Ok(IDLType::RecordT(fs))
    },
    "variant" "{" <mut fs:Sp<SepBy<VariantFieldTyp, ";">>> "}" =>? {
        let span = fs.1.clone();
        fs.0.sort_unstable_by_key(|TypeField { label, .. }| label.get_id());
        check_unique(fs.0.iter().map(|f| &f.label)).map_err(|e| error2(e, span))?;
        Ok(IDLType::VariantT(fs.0))
    },
    "func" <FuncTyp> => IDLType::FuncT(<>),
    "service" <ActorTyp> => IDLType::ServT(<>),
    "principal" => IDLType::PrincipalT,
}

PrimTyp: IDLType = {
    "null" => IDLType::PrimT(PrimType::Null),
    "id" => {
      match PrimType::str_to_enum(&<>) {
        Some(p) => IDLType::PrimT(p),
        None => IDLType::VarT(<>),
      }
    },
}

FieldTyp: TypeField = {
    <doc_comment:DocComment> <id:FieldId> ":" <typ:Typ> =>? Ok(TypeField { label: Label::Id(id), typ, docs: doc_comment.unwrap_or_default() }),
    <doc_comment:DocComment> <n:Name> ":" <typ:Typ> => TypeField { label: Label::Named(n), typ, docs: doc_comment.unwrap_or_default() },
}

RecordFieldTyp: TypeField = {
    FieldTyp => <>,
    <doc_comment:DocComment> <typ:Typ> => TypeField { label: Label::Unnamed(0), typ, docs: doc_comment.unwrap_or_default() },
}

VariantFieldTyp: TypeField = {
    FieldTyp => <>,
    <doc_comment:DocComment> <n:Name> => TypeField { label: Label::Named(n), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() },
    <doc_comment:DocComment> <id:FieldId> =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }),
}

TupTyp: Vec<IDLType> = "(" <SepBy<ArgTyp, ",">> ")" => <>;

FuncTyp: FuncType = {
    <args:TupTyp> "->" <rets:TupTyp> <modes:FuncMode*> =>
        FuncType { modes, args, rets },
}

ArgTyp: IDLType = {
    Typ => <>,
    Name ":" <Typ> => <>,
}

FuncMode: FuncMode = {
    "oneway" => FuncMode::Oneway,
    "query" => FuncMode::Query,
    "composite_query" => FuncMode::CompositeQuery,
}

ActorTyp: Vec<Binding> = {
    "{" <mut fs:Sp<SepBy<MethTyp, ";">>> "}" =>? {
        let span = fs.1.clone();
        fs.0.sort_unstable_by(|a,b| a.id.partial_cmp(&b.id).unwrap());
        check_unique(fs.0.iter().map(|f| &f.id)).map_err(|e| error2(e, span))?;
        Ok(fs.0)
    }
}

MethTyp: Binding = {
    <doc_comment: DocComment> <n:Name> ":" <f:FuncTyp> => Binding { id: n, typ: IDLType::FuncT(f), docs: doc_comment.unwrap_or_default() },
    <doc_comment: DocComment> <n:Name> ":" <id:"id"> => Binding { id: n, typ: IDLType::VarT(id), docs: doc_comment.unwrap_or_default() },
}

// Type declarations
Def: Dec = {
    <doc_comment: DocComment> "type" <id:"id"> "=" <t:Typ> => Dec::TypD(Binding { id: id, typ: t, docs: doc_comment.unwrap_or_default() }),
    "import" <Text> => Dec::ImportType(<>),
    "import" "service" <Text> => Dec::ImportServ(<>),
}

Actor: IDLType = {
    ActorTyp => IDLType::ServT(<>),
    "id" => IDLType::VarT(<>),
}

MainActor: IDLActorType = {
    <doc_comment: DocComment> "service" "id"? ":" <t:Actor> ";"? => IDLActorType { typ: t, docs: doc_comment.unwrap_or_default() },
    <doc_comment: DocComment> "service" "id"? ":" <args:TupTyp> "->" <t:Actor> ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() },
}

pub IDLProg: IDLProg = {
    <decs:SepBy<Def, ";">> <actor:MainActor?> => IDLProg { decs, actor }
}

pub IDLInitArgs: IDLInitArgs = {
    <decs:SepBy<Def, ";">> <args:TupTyp> => IDLInitArgs { decs, args }
}

// Test file. Follows the "specification" in test/README.md

Input: Input = {
    Text => Input::Text(<>),
    Bytes => Input::Blob(<>),
}

Assert: Assert = <id:Sp<"id">> <assert:Assertion> =>? {
    if id.0 != "assert" {
        Err(error2("not an assert", id.1))
    } else { Ok(assert) }
};

Assertion: Assert = {
    <left:Input> ":" <typ:TupTyp> <desc:Text?> => Assert { left, right: None, typ, pass: true, desc },
    <left:Input> "!:" <typ:TupTyp> <desc:Text?> => Assert { left, right: None, typ, pass: false, desc },
    <left:Input> "==" <right:Input> ":" <typ:TupTyp> <desc:Text?> => Assert { left, right: Some(right), typ, pass: true, desc },
    <left:Input> "!=" <right:Input> ":" <typ:TupTyp> <desc:Text?> => Assert { left, right: Some(right), typ, pass: false, desc },
}

pub Test: Test = {
    <defs:SepBy<Def, ";">> <asserts:SepBy<Assert, ";">> => Test { defs, asserts },
}

// Common util
Name: String = {
    "id" => <>,
    Text => <>,
}

// Also allows trailing separator
#[inline]
SepBy<T, S>: Vec<T> = {
    <mut v:(<T> S)*> <e:T?> => match e {
        None => v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};

#[inline]
Sp<T>: (T, Span) =
    <l: @L> <t: T> <r: @R> => (t, l..r);

#[inline]
DocComment: Option<Vec<String>> =
    <l: @L> => {
      trivia.and_then(|t| t.borrow().get(&l).cloned())
    };