mod method;
mod state;
use crate::utils::{
attribute_tokens, hide_ident, impl_macro_attribute_tokens, to_pascal_case, to_pascal_case_ext,
to_snake_case_ext, trait_macro_attribute_tokens,
};
use method::SelfKind;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use state::ServiceState;
use syn::{
braced, bracketed, meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, token,
Attribute, GenericParam, Generics, Ident, LitStr, Path, PathSegment, Token, TypeParamBound,
TypePath, Visibility, WhereClause,
};
pub struct Service {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub trait_token: Token![trait],
pub ident: Ident,
pub generics: Generics,
pub colon_token: Option<Token![:]>,
pub supertraits: Punctuated<TypeParamBound, Token![+]>,
pub brace_token: token::Brace,
pub items: Vec<ServiceItem>,
pub meta: ServiceMeta,
}
impl Service {
pub fn parse_meta(&mut self, meta: ParseNestedMeta) -> syn::Result<()> {
let ident = meta.path.get_ident().map(|i| i.to_string());
match ident.as_ref().map(|s| s.as_str()) {
Some("host_feature") => {
let host_feature: LitStr = meta.input.parse()?;
self.meta.host_feature = Some(host_feature.value());
Ok(())
}
Some("targets") => {
let _eq_token: Token![=] = meta.input.parse()?;
let content;
bracketed!(content in meta.input);
let targets: Punctuated<ServiceMetaTarget, Token![,]> =
Punctuated::parse_separated_nonempty(&content)?;
self.meta.targets = targets.into_iter().collect();
Ok(())
}
Some("states") => {
let _eq_token: Token![=] = meta.input.parse()?;
let content;
bracketed!(content in meta.input);
let targets: Punctuated<ServiceState, Token![,]> =
Punctuated::parse_separated_nonempty(&content)?;
self.meta.states = targets.into_iter().collect();
Ok(())
}
_ => Err(meta.error("unknown attribute")),
}
}
pub fn trait_def(&self) -> TokenStream2 {
let macro_attribute = trait_macro_attribute_tokens(true);
let mut trait_def_tokens = quote! {};
attribute_tokens(&self.attrs).to_tokens(&mut trait_def_tokens);
self.vis.to_tokens(&mut trait_def_tokens);
self.trait_token.to_tokens(&mut trait_def_tokens);
self.ident.to_tokens(&mut trait_def_tokens);
self.generics.to_tokens(&mut trait_def_tokens);
if self.contains_states() {
let colon_token = self
.colon_token
.unwrap_or(syn::parse2(quote! { : }).unwrap());
let state_trait_ident = self.state_trait_ident();
let (_, ty_generics, _) = self.generics.split_for_impl();
let mut supertraits = self.supertraits.clone();
if self.generics.params.len() > 0 {
supertraits.push(syn::parse2(quote! { #state_trait_ident #ty_generics }).unwrap());
} else {
supertraits.push(syn::parse2(quote! { #state_trait_ident }).unwrap());
}
colon_token.to_tokens(&mut trait_def_tokens);
supertraits.to_tokens(&mut trait_def_tokens);
} else {
self.colon_token.to_tokens(&mut trait_def_tokens);
self.supertraits.to_tokens(&mut trait_def_tokens);
}
self.brace_token.surround(&mut trait_def_tokens, |tokens| {
for item in &self.items {
item.to_trait_def_item(tokens);
}
});
quote! {
#macro_attribute
#trait_def_tokens
}
}
pub fn state_trait_def(&self) -> TokenStream2 {
if !self.contains_states() {
return quote! {};
}
let macro_attribute = trait_macro_attribute_tokens(true);
let mut trait_def_tokens = quote! {};
attribute_tokens(&self.attrs).to_tokens(&mut trait_def_tokens);
self.vis.to_tokens(&mut trait_def_tokens);
self.trait_token.to_tokens(&mut trait_def_tokens);
self.state_trait_ident().to_tokens(&mut trait_def_tokens);
self.generics.to_tokens(&mut trait_def_tokens);
self.colon_token.to_tokens(&mut trait_def_tokens);
self.brace_token.surround(&mut trait_def_tokens, |tokens| {
for item in &self.meta.states {
item.to_state_trait_def_item(tokens);
}
});
quote! {
#macro_attribute
#trait_def_tokens
}
}
pub fn state_set_def(&self) -> TokenStream2 {
if !self.contains_states() {
return quote! {};
}
let Self {
vis,
attrs,
generics,
..
} = self;
let attrs = attribute_tokens(attrs);
let state_trait_ident = self.state_trait_ident();
let state_set_ident = self.state_set_ident();
let ty_generics_list = self.generics_param_names(false);
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let fields = self.meta.states.iter().map(ServiceState::state_set_field);
let new_args = self.meta.states.iter().map(ServiceState::function_argument);
let ctor_args = self.meta.states.iter().map(ServiceState::ctor_argument);
let mut trait_generics = self.generics.clone();
trait_generics
.params
.insert(0, syn::parse2(quote! { Target }).unwrap());
trait_generics
.make_where_clause()
.predicates
.extend(syn::parse2::<WhereClause>(
quote! { where Target: ::dremoc::service::Stateful<StateSet=#state_set_ident #ty_generics> + ::std::marker::Send + ::std::marker::Sync + 'static },
)
.unwrap().predicates);
let (trait_impl_generics, _, trait_where_clause) = trait_generics.split_for_impl();
let auto_impl_methods = self
.meta
.states
.iter()
.map(ServiceState::stateful_auto_impl_methods);
let impl_macro_attribute = impl_macro_attribute_tokens(true);
quote! {
#[derive(Clone, ::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
#attrs
#vis struct #state_set_ident #generics {
#(
#vis #fields,
)*
__phantom: ::std::marker::PhantomData<(#ty_generics_list)>,
}
impl #impl_generics #state_set_ident #ty_generics #where_clause {
#vis fn new(#(#new_args,)*) -> Self {
Self {
#(
#ctor_args,
)*
__phantom: ::std::marker::PhantomData,
}
}
}
#impl_macro_attribute
impl #trait_impl_generics #state_trait_ident #ty_generics for Target #trait_where_clause {
#(
#auto_impl_methods
)*
}
}
}
pub fn interns_mod_name(&self) -> Ident {
hide_ident(&to_snake_case_ext(&self.ident, "", "service interns"))
}
pub fn request_enums_def(&self) -> TokenStream2 {
let Self {
ident, generics, ..
} = self;
let ty_generics_list = self.generics_param_names(false);
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let (main_req, value_req, ref_req, ref_mut_req) = self.request_enum_idents();
let (mut ref_entries, mut ref_clauses, mut ref_into) = (quote! {}, quote! {}, quote! {});
let (mut ref_mut_entries, mut ref_mut_clauses, mut ref_mut_into) =
(quote! {}, quote! {}, quote! {});
for md in self.methods(true, true) {
match md.sig.self_kind {
SelfKind::Ref { .. } => {
ref_entries.append_all(md.request_enum_variant());
ref_clauses.append_all(md.dispatch_variant());
ref_into.append_all(md.variant_to_req(&main_req, &ref_req, &ref_mut_req));
}
SelfKind::RefMut { .. } => {
ref_mut_entries.append_all(md.request_enum_variant());
ref_mut_clauses.append_all(md.dispatch_variant());
ref_mut_into.append_all(md.variant_to_req(&main_req, &ref_req, &ref_mut_req));
}
}
}
let target_requirements = if self.contains_states() {
let state_trait_ident = self.state_trait_ident();
quote! { #ident #ty_generics + #state_trait_ident #ty_generics }
} else {
quote! { #ident #ty_generics }
};
quote! {
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
enum #main_req #generics {
#ref_entries
#ref_mut_entries
#[serde(skip)]
__Phantom(::std::marker::PhantomData<(#ty_generics_list)>)
}
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
enum #value_req #generics {
#[serde(skip)]
__Phantom(::std::marker::PhantomData<(#ty_generics_list)>)
}
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
enum #ref_req #generics {
#ref_entries
#[serde(skip)]
__Phantom(::std::marker::PhantomData<(#ty_generics_list)>)
}
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
enum #ref_mut_req #generics {
#ref_mut_entries
#[serde(skip)]
__Phantom(::std::marker::PhantomData<(#ty_generics_list)>)
}
impl #impl_generics #ref_req #ty_generics #where_clause {
#[inline]
async fn dispatch<Target>(self, target: &Target) where Target: #target_requirements {
match self {
#ref_clauses
Self::__Phantom(_) => ()
}
}
}
impl #impl_generics #ref_mut_req #ty_generics #where_clause {
#[inline]
async fn dispatch<Target>(self, target: &mut Target) where Target: #target_requirements {
match self {
#ref_mut_clauses
Self::__Phantom(_) => ()
}
}
}
impl #impl_generics Into<
::dremoc::remoc::rtc::Req<
#value_req #ty_generics,
#ref_req #ty_generics,
#ref_mut_req #ty_generics
>
> for #main_req #ty_generics #where_clause {
#[inline]
fn into(self) -> ::dremoc::remoc::rtc::Req<
#value_req #ty_generics,
#ref_req #ty_generics,
#ref_mut_req #ty_generics
> {
match self {
#ref_into
#ref_mut_into
Self::__Phantom(_) => ::dremoc::remoc::rtc::Req::Value(#value_req::__Phantom(::std::marker::PhantomData))
}
}
}
}
}
pub fn client_def(&self) -> TokenStream2 {
let Self {
ident,
attrs,
generics,
..
} = self;
let attrs = attribute_tokens(attrs);
let client_ident = self.client_ident();
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let (main_req, ..) = self.request_enum_idents();
let mut methods = quote! {};
for m in self.methods(true, false) {
methods.append_all(m.client_method(&main_req));
}
let impl_macro_attribute = impl_macro_attribute_tokens(true);
let state_trait_impl = if self.contains_states() {
let state_trait_ident = self.state_trait_ident();
let mut state_methods = quote! {};
for m in self.methods(false, true) {
state_methods.append_all(m.client_method(&main_req));
}
quote! {
#impl_macro_attribute
impl #impl_generics #state_trait_ident #ty_generics for #client_ident #ty_generics #where_clause {
#state_methods
}
}
} else {
quote! {}
};
quote! {
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
#attrs
pub struct #client_ident #generics {
req_tx: ::dremoc::sync::mpsc::Sender<#main_req #ty_generics>,
#[serde(
default = "::dremoc::remoc::rtc::missing_max_reply_size",
with = "::dremoc::remoc::rtc::serde_max_reply_size"
)]
max_reply_size: usize,
}
impl #impl_generics #client_ident #ty_generics #where_clause {
fn new(req_tx: ::dremoc::sync::mpsc::Sender<#main_req #ty_generics>) -> Self {
Self {
req_tx,
max_reply_size: ::dremoc::remoc::rch::DEFAULT_MAX_ITEM_SIZE,
}
}
}
#impl_macro_attribute
impl #impl_generics #ident #ty_generics for #client_ident #ty_generics #where_clause {
#methods
}
#state_trait_impl
impl #impl_generics Clone for #client_ident #ty_generics #where_clause {
fn clone(&self) -> Self {
Self {
req_tx: self.req_tx.clone(),
max_reply_size: self.max_reply_size,
}
}
}
}
}
pub fn target_def(&self) -> TokenStream2 {
let Self {
ident,
attrs,
generics,
..
} = self;
let attrs = attribute_tokens(attrs);
let target_ident = self.target_ident();
let host_feature = self.host_feature();
let ty_generics_list = self.generics_param_names(false);
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let variants = self.meta.targets.iter().map(|target| {
let variant_ident = to_pascal_case(target.ident());
let ty = target.path();
quote! { #variant_ident(#ty) }
});
let mut methods = quote! {};
let trait_path: TypePath = if self.generics.params.len() > 0 {
syn::parse2(quote! { #ident::#ty_generics }).unwrap()
} else {
syn::parse2(quote! { #ident }).unwrap()
};
for m in self.methods(true, false) {
methods.append_all(
m.target_method(
&trait_path,
self.meta
.targets
.iter()
.map(|target| to_pascal_case(target.ident())),
),
);
}
let impl_macro_attribute = impl_macro_attribute_tokens(true);
let state_trait_impl = if self.contains_states() {
let state_trait_ident = self.state_trait_ident();
let state_trait_path: TypePath = if self.generics.params.len() > 0 {
syn::parse2(quote! { #state_trait_ident::#ty_generics }).unwrap()
} else {
syn::parse2(quote! { #state_trait_ident }).unwrap()
};
let mut state_methods = quote! {};
for m in self.methods(false, true) {
state_methods.append_all(
m.target_method(
&state_trait_path,
self.meta
.targets
.iter()
.map(|target| to_pascal_case(target.ident())),
),
);
}
quote! {
#[cfg(feature = #host_feature)]
#impl_macro_attribute
impl #impl_generics #state_trait_ident #ty_generics for #target_ident #ty_generics #where_clause {
#state_methods
}
}
} else {
quote! {}
};
quote! {
#[cfg(feature = #host_feature)]
#attrs
pub enum #target_ident #generics {
#(#variants),*,
__Phantom(::std::marker::PhantomData<(#ty_generics_list)>)
}
#[cfg(feature = #host_feature)]
#impl_macro_attribute
impl #impl_generics #ident #ty_generics for #target_ident #ty_generics #where_clause {
#methods
}
#state_trait_impl
}
}
pub fn handle_def(&self) -> (TokenStream2, TokenStream2) {
let Self {
vis,
ident,
attrs,
generics,
..
} = self;
let attrs = attribute_tokens(attrs);
let interns_mod_name = self.interns_mod_name();
let target_ident = self.target_ident();
let client_ident = self.client_ident();
let handle_ident = self.handle_ident();
let host_feature = self.host_feature();
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let (main_req, ..) = self.request_enum_idents();
let mut methods = quote! {};
for m in self.methods(true, false) {
methods.append_all(m.handle_method(&host_feature));
}
let variants_new = self.meta.targets.iter().map(|target| {
let fn_ident = to_snake_case_ext(target.ident(), "new", "");
let variant_ident = to_pascal_case(target.ident());
let ty = target.path();
quote! {
pub fn #fn_ident(target: #ty, request_buffer: usize) -> ::dremoc::sync::RwLock<Self> {
return Self::new(#target_ident::#variant_ident(target), request_buffer)
}
}
});
let impl_macro_attribute = impl_macro_attribute_tokens(true);
let state_trait_impl = if self.contains_states() {
let state_trait_ident = self.state_trait_ident();
let mut state_methods = quote! {};
for m in self.methods(false, true) {
state_methods.append_all(m.handle_method(&host_feature));
}
quote! {
#impl_macro_attribute
impl #impl_generics #state_trait_ident #ty_generics for #handle_ident #ty_generics #where_clause {
#state_methods
}
}
} else {
quote! {}
};
(
quote! {
#[derive(::dremoc::remoc::_serde::Serialize, ::dremoc::remoc::_serde::Deserialize)]
#[serde(crate = "::dremoc::remoc::_serde")]
#[serde(bound(serialize = ""))]
#[serde(bound(deserialize = ""))]
#attrs
#vis struct #handle_ident #generics {
#[cfg(feature = #host_feature)]
#[serde(skip)]
target: Option<#interns_mod_name::#target_ident #ty_generics>,
client: #interns_mod_name::#client_ident #ty_generics
}
#impl_macro_attribute
impl #impl_generics #ident #ty_generics for #handle_ident #ty_generics #where_clause {
#methods
}
#state_trait_impl
impl #impl_generics Clone for #handle_ident #ty_generics #where_clause {
fn clone(&self) -> Self {
Self {
#[cfg(feature = #host_feature)]
target: None,
client: self.client.clone()
}
}
}
},
quote! {
#[cfg(feature = #host_feature)]
impl #impl_generics #handle_ident #ty_generics #where_clause {
#(#variants_new)*
fn new(target: #target_ident #ty_generics, request_buffer: usize) -> ::dremoc::sync::RwLock<Self> {
let (req_tx, req_rx) = ::dremoc::sync::mpsc::channel(request_buffer);
let handle = ::dremoc::sync::RwLock::new_frivolous(Self {
target: Some(target),
client: #client_ident::new(req_tx)
});
let target = ::std::sync::Arc::downgrade(handle.host().unwrap());
::dremoc::tokio::spawn(async move {
Self::handle_host_requests(target, req_rx).await
});
handle
}
async fn handle_host_requests(
this: ::std::sync::Weak<::dremoc::tokio::sync::RwLock<Self>>,
mut req_rx: ::dremoc::sync::mpsc::Receiver<#main_req #ty_generics>
) {
loop {
let req = req_rx.recv().await
.map(|req|
req.map(|req| req.into())
);
let Some(this) = this.upgrade() else {
break
};
match req {
Ok(Some(::dremoc::remoc::rtc::Req::Ref(req))) => {
let this = this.read_owned().await;
::dremoc::tokio::spawn(async move {
let target = this.target.as_ref().unwrap();
req.dispatch(target).await;
});
},
Ok(Some(::dremoc::remoc::rtc::Req::RefMut(req))) => {
let mut this = this.write_owned().await;
::dremoc::tokio::spawn(async move {
let target = this.target.as_mut().unwrap();
req.dispatch(target).await;
});
}
Ok(Some(_)) => (),
Ok(None) => break,
Err(err) if err.is_final() => break,
Err(err) => ::dremoc::remoc::rtc::receiving_request_failed(err),
}
}
}
}
},
)
}
fn generics_param_names(&self, with_const: bool) -> Punctuated<Ident, Token![,]> {
let (ty_generics, _) = self.generics();
ty_generics
.params
.iter()
.filter_map(|p| match (p, with_const) {
(GenericParam::Type(p), _) => Some(p.ident.clone()),
(GenericParam::Const(p), true) => Some(p.ident.clone()),
_ => None,
})
.collect()
}
fn generics(&self) -> (Generics, Generics) {
let ty_generics = self.generics.clone();
let impl_generics = ty_generics.clone();
(ty_generics, impl_generics)
}
fn request_enum_idents(&self) -> (Ident, Ident, Ident, Ident) {
(
to_pascal_case_ext(&self.ident, "", "Req"),
to_pascal_case_ext(&self.ident, "", "ValueReq"),
to_pascal_case_ext(&self.ident, "", "RefReq"),
to_pascal_case_ext(&self.ident, "", "RefMutReq"),
)
}
fn methods(
&self,
trait_methods: bool,
state_methods: bool,
) -> impl Iterator<Item = &method::ServiceMethod> {
trait_methods
.then(|| self.items.iter().filter_map(ServiceItem::as_method))
.into_iter()
.flatten()
.chain(
state_methods
.then(|| self.meta.states.iter().flat_map(ServiceState::methods))
.into_iter()
.flatten(),
)
}
fn contains_states(&self) -> bool {
self.meta.states.len() > 0
}
fn state_trait_ident(&self) -> Ident {
to_pascal_case_ext(&self.ident, "", "state")
}
fn state_set_ident(&self) -> Ident {
to_pascal_case_ext(&self.ident, "", "state set")
}
fn client_ident(&self) -> Ident {
to_pascal_case_ext(&self.ident, "", "client")
}
fn target_ident(&self) -> Ident {
to_pascal_case_ext(&self.ident, "", "target")
}
fn handle_ident(&self) -> Ident {
to_pascal_case_ext(&self.ident, "", "handle")
}
fn host_feature(&self) -> String {
self.meta.host_feature.clone().unwrap_or("host".to_string())
}
}
impl Parse for Service {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis = input.parse()?;
let trait_token = input.parse()?;
let ident = input.parse()?;
let mut generics: Generics = input.parse()?;
let colon_token: Option<Token![:]> = input.parse()?;
let mut supertraits = Punctuated::new();
if colon_token.is_some() {
loop {
if input.peek(Token![where]) || input.peek(token::Brace) {
break;
}
supertraits.push_value(input.parse()?);
if input.peek(Token![where]) || input.peek(token::Brace) {
break;
}
supertraits.push_punct(input.parse()?);
}
}
generics.where_clause = input.parse()?;
let content;
let brace_token = braced!(content in input);
let mut items = Vec::new();
while !content.is_empty() {
items.push(content.parse()?);
}
Ok(Self {
attrs,
vis,
trait_token,
ident,
generics,
colon_token,
supertraits,
brace_token,
items,
meta: ServiceMeta::default(),
})
}
}
impl ToTokens for Service {
fn to_tokens(&self, tokens: &mut TokenStream2) {
attribute_tokens(&self.attrs).to_tokens(tokens);
self.vis.to_tokens(tokens);
self.trait_token.to_tokens(tokens);
self.ident.to_tokens(tokens);
self.generics.to_tokens(tokens);
self.colon_token.to_tokens(tokens);
self.supertraits.to_tokens(tokens);
self.brace_token.surround(tokens, |tokens| {
for item in &self.items {
item.to_tokens(tokens);
}
});
}
}
#[derive(Default)]
pub struct ServiceMeta {
pub host_feature: Option<String>,
pub targets: Vec<ServiceMetaTarget>,
pub states: Vec<ServiceState>,
}
pub enum ServiceMetaTarget {
Path(PathSegment),
Rename { path: Path, ident: Ident },
}
impl ServiceMetaTarget {
pub fn ident(&self) -> &Ident {
match self {
Self::Path(path) => &path.ident,
Self::Rename { ident, .. } => ident,
}
}
pub fn path(&self) -> Path {
match self {
Self::Path(path) => {
let mut segments = Punctuated::new();
segments.push(path.clone());
Path {
leading_colon: None,
segments,
}
}
Self::Rename { path, .. } => path.clone(),
}
}
}
impl Parse for ServiceMetaTarget {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek2(Token![as]) {
let path = input.parse()?;
let _as_token: Token![as] = input.parse()?;
let ident = input.parse()?;
Ok(Self::Rename { path, ident })
} else {
let path = input.parse()?;
Ok(Self::Path(path))
}
}
}
pub enum ServiceItem {
Method(method::ServiceMethod),
}
impl ServiceItem {
pub fn as_method(&self) -> Option<&method::ServiceMethod> {
match self {
Self::Method(method) => Some(method),
}
}
pub fn to_trait_def_item(&self, tokens: &mut TokenStream2) {
match self {
Self::Method(method) => method.to_tokens(tokens),
}
}
}
impl Parse for ServiceItem {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self::Method(input.parse()?))
}
}
impl ToTokens for ServiceItem {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Method(method) => method.to_tokens(tokens),
}
}
}