Skip to main content

bern_syntax/
lib.rs

1//! The one parser for Bern declarations.
2//!
3//! Procedural macros use it to expand declarations for `rustc`, and the
4//! metadata collector uses it to read the same source files without running
5//! application code. Keeping a single parser means both sides always agree
6//! on what a declaration means.
7
8pub mod ast;
9pub mod build;
10mod expr;
11pub mod items;
12pub mod lower;
13pub mod style;
14mod ui;
15
16pub use ast::*;
17pub use items::{
18    CommandItem, CommandParam, CommandRef, CommandsItem, ComponentItem, EventItem, EventsItem,
19    FeatureItem, FeaturesItem, FieldItem, Param, ParamKind, PlayerField, PlayersItem, ProtocolArgs,
20    ProtocolItem, QueriesItem, QueryItem, SessionItem, SourceItems, StateItem, ValueItem,
21    ValueKind, VariantItem, doc_comment, is_bern_attr, parse_component, parse_component_args,
22    parse_component_with, parse_features, parse_players, parse_protocol, parse_protocol_args,
23    parse_session, parse_source_file, parse_value, parse_value_args, type_ref,
24};
25pub use style::{
26    CornerBlock, MotionDecl, StyleArg, StyleItem, StyleProp, StyleRuleItem, StyleValue, parse_style,
27};
28
29/// Parse the body of a `ui!` invocation.
30pub fn parse_ui(tokens: proc_macro2::TokenStream) -> syn::Result<UiBody> {
31    syn::parse2(tokens)
32}
33
34/// Parse a standalone `tr!` invocation body.
35pub fn parse_tr(tokens: proc_macro2::TokenStream) -> syn::Result<Tr> {
36    syn::parse2::<expr::TrBody>(tokens).map(|body| body.0)
37}
38
39/// A number literal's value. One too large for `f64` is an error, as in
40/// Rust, so the IR holds no infinity a target would have to write.
41pub(crate) fn finite(value: f64, span: proc_macro2::Span) -> syn::Result<f64> {
42    if value.is_finite() {
43        Ok(value)
44    } else {
45        Err(syn::Error::new(
46            span,
47            "number literal is out of range for f64",
48        ))
49    }
50}
51
52/// Lower snake case identifiers used for ids: `preview`, `main_window`.
53pub fn valid_id(name: &str) -> bool {
54    bern_protocol::valid_name(name)
55}
56
57/// Whether a type mentions the named type anywhere.
58pub fn mentions(ty: &TypeRef, name: &str) -> bool {
59    match ty {
60        TypeRef::Named(path) => path.last().is_some_and(|last| last == name),
61        TypeRef::Optional(inner) | TypeRef::List(inner) => mentions(inner, name),
62        _ => false,
63    }
64}
65
66/// Call `visit` with the name of each named type `ty` refers to.
67pub fn named(ty: &TypeRef, visit: &mut impl FnMut(&str)) {
68    match ty {
69        TypeRef::Named(path) => {
70            if let Some(last) = path.last() {
71                visit(last);
72            }
73        }
74        TypeRef::Optional(inner) | TypeRef::List(inner) => named(inner, visit),
75        _ => {}
76    }
77}
78
79/// Convert `snake_case` to `PascalCase`.
80pub fn pascal_case(name: &str) -> String {
81    name.split('_')
82        .filter(|part| !part.is_empty())
83        .map(|part| {
84            let mut chars = part.chars();
85            match chars.next() {
86                Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
87                None => String::new(),
88            }
89        })
90        .collect()
91}
92
93/// Convert `snake_case` to `camelCase`.
94pub fn camel_case(name: &str) -> String {
95    let pascal = pascal_case(name);
96    let mut chars = pascal.chars();
97    match chars.next() {
98        Some(first) => first.to_lowercase().chain(chars).collect(),
99        None => String::new(),
100    }
101}
102
103#[cfg(test)]
104mod tests;