| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| use std::borrow::Cow; |
| use std::collections::HashMap; |
| use std::io::Write; |
| |
| use syn::ext::IdentExt; |
| use syn::{self, UnOp}; |
| |
| use crate::bindgen::config::{Config, Language}; |
| use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; |
| use crate::bindgen::dependencies::Dependencies; |
| use crate::bindgen::ir::{ |
| AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path, |
| Struct, ToCondition, Type, |
| }; |
| use crate::bindgen::library::Library; |
| use crate::bindgen::writer::{Source, SourceWriter}; |
| use crate::bindgen::Bindings; |
| |
| fn member_to_ident(member: &syn::Member) -> String { |
| match member { |
| syn::Member::Named(ref name) => name.unraw().to_string(), |
| syn::Member::Unnamed(ref index) => format!("_{}", index.index), |
| } |
| } |
| |
| // TODO: Maybe add support to more std associated constants. |
| fn to_known_assoc_constant(associated_to: &Path, name: &str) -> Option<String> { |
| use crate::bindgen::ir::{IntKind, PrimitiveType}; |
| |
| if name != "MAX" && name != "MIN" { |
| return None; |
| } |
| |
| let prim = PrimitiveType::maybe(associated_to.name())?; |
| let prefix = match prim { |
| PrimitiveType::Integer { |
| kind, |
| signed, |
| zeroable: _, |
| } => match kind { |
| IntKind::B8 => { |
| if signed { |
| "INT8" |
| } else { |
| "UINT8" |
| } |
| } |
| IntKind::B16 => { |
| if signed { |
| "INT16" |
| } else { |
| "UINT16" |
| } |
| } |
| IntKind::B32 => { |
| if signed { |
| "INT32" |
| } else { |
| "UINT32" |
| } |
| } |
| IntKind::B64 => { |
| if signed { |
| "INT64" |
| } else { |
| "UINT64" |
| } |
| } |
| _ => return None, |
| }, |
| _ => return None, |
| }; |
| Some(format!("{}_{}", prefix, name)) |
| } |
| |
| #[derive(Debug, Clone)] |
| pub enum Literal { |
| Expr(String), |
| Path { |
| associated_to: Option<(Path, String)>, |
| name: String, |
| }, |
| PostfixUnaryOp { |
| op: &'static str, |
| value: Box<Literal>, |
| }, |
| BinOp { |
| left: Box<Literal>, |
| op: &'static str, |
| right: Box<Literal>, |
| }, |
| FieldAccess { |
| base: Box<Literal>, |
| field: String, |
| }, |
| Struct { |
| path: Path, |
| export_name: String, |
| fields: HashMap<String, Literal>, |
| }, |
| Cast { |
| ty: Type, |
| value: Box<Literal>, |
| }, |
| } |
| |
| impl Literal { |
| fn replace_self_with(&mut self, self_ty: &Path) { |
| match *self { |
| Literal::PostfixUnaryOp { ref mut value, .. } => { |
| value.replace_self_with(self_ty); |
| } |
| Literal::BinOp { |
| ref mut left, |
| ref mut right, |
| .. |
| } => { |
| left.replace_self_with(self_ty); |
| right.replace_self_with(self_ty); |
| } |
| Literal::FieldAccess { ref mut base, .. } => { |
| base.replace_self_with(self_ty); |
| } |
| Literal::Struct { |
| ref mut path, |
| ref mut export_name, |
| ref mut fields, |
| } => { |
| if path.replace_self_with(self_ty) { |
| *export_name = self_ty.name().to_owned(); |
| } |
| for ref mut expr in fields.values_mut() { |
| expr.replace_self_with(self_ty); |
| } |
| } |
| Literal::Cast { |
| ref mut ty, |
| ref mut value, |
| } => { |
| ty.replace_self_with(self_ty); |
| value.replace_self_with(self_ty); |
| } |
| Literal::Path { |
| ref mut associated_to, |
| .. |
| } => { |
| if let Some((ref mut path, ref mut export_name)) = *associated_to { |
| if path.replace_self_with(self_ty) { |
| *export_name = self_ty.name().to_owned(); |
| } |
| } |
| } |
| Literal::Expr(..) => {} |
| } |
| } |
| |
| fn is_valid(&self, bindings: &Bindings) -> bool { |
| match *self { |
| Literal::Expr(..) => true, |
| Literal::Path { |
| ref associated_to, |
| ref name, |
| } => { |
| if let Some((ref path, _export_name)) = associated_to { |
| return bindings.struct_exists(path) |
| || to_known_assoc_constant(path, name).is_some(); |
| } |
| true |
| } |
| Literal::PostfixUnaryOp { ref value, .. } => value.is_valid(bindings), |
| Literal::BinOp { |
| ref left, |
| ref right, |
| .. |
| } => left.is_valid(bindings) && right.is_valid(bindings), |
| Literal::FieldAccess { ref base, .. } => base.is_valid(bindings), |
| Literal::Struct { ref path, .. } => bindings.struct_exists(path), |
| Literal::Cast { ref value, .. } => value.is_valid(bindings), |
| } |
| } |
| |
| fn can_be_constexpr(&self) -> bool { |
| !self.has_pointer_casts() |
| } |
| |
| fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool { |
| if !visitor(self) { |
| return false; |
| } |
| match self { |
| Literal::Expr(..) | Literal::Path { .. } => true, |
| Literal::PostfixUnaryOp { ref value, .. } => value.visit(visitor), |
| Literal::BinOp { |
| ref left, |
| ref right, |
| .. |
| } => left.visit(visitor) && right.visit(visitor), |
| Literal::FieldAccess { ref base, .. } => base.visit(visitor), |
| Literal::Struct { ref fields, .. } => { |
| for (_name, field) in fields.iter() { |
| if !field.visit(visitor) { |
| return false; |
| } |
| } |
| true |
| } |
| Literal::Cast { ref value, .. } => value.visit(visitor), |
| } |
| } |
| |
| fn has_pointer_casts(&self) -> bool { |
| let mut has_pointer_casts = false; |
| self.visit(&mut |lit| { |
| if let Literal::Cast { ref ty, .. } = *lit { |
| has_pointer_casts = has_pointer_casts || ty.is_ptr(); |
| } |
| !has_pointer_casts |
| }); |
| has_pointer_casts |
| } |
| |
| pub fn uses_only_primitive_types(&self) -> bool { |
| let mut uses_only_primitive_types = true; |
| self.visit(&mut |lit| { |
| // XXX This is a bit sketchy, but alas. |
| uses_only_primitive_types = uses_only_primitive_types |
| && match *lit { |
| Literal::Struct { .. } => false, |
| Literal::Cast { ref ty, .. } => ty.is_primitive_or_ptr_primitive(), |
| _ => true, |
| }; |
| uses_only_primitive_types |
| }); |
| uses_only_primitive_types |
| } |
| } |
| |
| impl Literal { |
| pub fn rename_for_config(&mut self, config: &Config) { |
| match self { |
| Literal::Struct { |
| ref mut export_name, |
| fields, |
| .. |
| } => { |
| config.export.rename(export_name); |
| for lit in fields.values_mut() { |
| lit.rename_for_config(config); |
| } |
| } |
| Literal::FieldAccess { ref mut base, .. } => { |
| base.rename_for_config(config); |
| } |
| Literal::Path { |
| ref mut associated_to, |
| ref mut name, |
| } => { |
| if let Some((_path, ref mut export_name)) = associated_to { |
| config.export.rename(export_name); |
| } else { |
| config.export.rename(name); |
| } |
| } |
| Literal::PostfixUnaryOp { ref mut value, .. } => { |
| value.rename_for_config(config); |
| } |
| Literal::BinOp { |
| ref mut left, |
| ref mut right, |
| .. |
| } => { |
| left.rename_for_config(config); |
| right.rename_for_config(config); |
| } |
| Literal::Expr(_) => {} |
| Literal::Cast { |
| ref mut ty, |
| ref mut value, |
| } => { |
| ty.rename_for_config(config, &GenericParams::default()); |
| value.rename_for_config(config); |
| } |
| } |
| } |
| |
| // Translate from full blown `syn::Expr` into a simpler `Literal` type |
| pub fn load(expr: &syn::Expr) -> Result<Literal, String> { |
| match *expr { |
| // Match binary expressions of the form `a * b` |
| syn::Expr::Binary(ref bin_expr) => { |
| let l = Self::load(&bin_expr.left)?; |
| let r = Self::load(&bin_expr.right)?; |
| let op = match bin_expr.op { |
| syn::BinOp::Add(..) => "+", |
| syn::BinOp::Sub(..) => "-", |
| syn::BinOp::Mul(..) => "*", |
| syn::BinOp::Div(..) => "/", |
| syn::BinOp::Rem(..) => "%", |
| syn::BinOp::And(..) => "&&", |
| syn::BinOp::Or(..) => "||", |
| syn::BinOp::BitXor(..) => "^", |
| syn::BinOp::BitAnd(..) => "&", |
| syn::BinOp::BitOr(..) => "|", |
| syn::BinOp::Shl(..) => "<<", |
| syn::BinOp::Shr(..) => ">>", |
| syn::BinOp::Eq(..) => "==", |
| syn::BinOp::Lt(..) => "<", |
| syn::BinOp::Le(..) => "<=", |
| syn::BinOp::Ne(..) => "!=", |
| syn::BinOp::Ge(..) => ">=", |
| syn::BinOp::Gt(..) => ">", |
| syn::BinOp::AddEq(..) => "+=", |
| syn::BinOp::SubEq(..) => "-=", |
| syn::BinOp::MulEq(..) => "*=", |
| syn::BinOp::DivEq(..) => "/=", |
| syn::BinOp::RemEq(..) => "%=", |
| syn::BinOp::BitXorEq(..) => "^=", |
| syn::BinOp::BitAndEq(..) => "&=", |
| syn::BinOp::BitOrEq(..) => "|=", |
| syn::BinOp::ShlEq(..) => ">>=", |
| syn::BinOp::ShrEq(..) => "<<=", |
| }; |
| Ok(Literal::BinOp { |
| left: Box::new(l), |
| op, |
| right: Box::new(r), |
| }) |
| } |
| |
| // Match literals like true, 'a', 32 etc |
| syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { |
| match lit { |
| syn::Lit::Byte(ref value) => Ok(Literal::Expr(format!("{}", value.value()))), |
| syn::Lit::Char(ref value) => Ok(Literal::Expr(match value.value() as u32 { |
| 0..=255 => format!("'{}'", value.value().escape_default()), |
| other_code => format!(r"U'\U{:08X}'", other_code), |
| })), |
| syn::Lit::Int(ref value) => { |
| let suffix = match value.suffix() { |
| "u64" => "ull", |
| "i64" => "ll", |
| "u32" => "u", |
| _ if value.base10_parse::<i64>().is_err() => "ull", |
| _ => "", |
| }; |
| Ok(Literal::Expr(format!( |
| "{}{}", |
| value.base10_digits(), |
| suffix |
| ))) |
| } |
| syn::Lit::Float(ref value) => { |
| Ok(Literal::Expr(value.base10_digits().to_string())) |
| } |
| syn::Lit::Bool(ref value) => Ok(Literal::Expr(format!("{}", value.value))), |
| // TODO: Add support for byte string and Verbatim |
| _ => Err(format!("Unsupported literal expression. {:?}", *lit)), |
| } |
| } |
| |
| syn::Expr::Field(syn::ExprField { |
| ref base, |
| ref member, |
| .. |
| }) => Ok(Literal::FieldAccess { |
| base: Box::new(Literal::load(base)?), |
| field: member_to_ident(member), |
| }), |
| |
| syn::Expr::Call(syn::ExprCall { |
| ref func, ref args, .. |
| }) => { |
| let struct_name = match Literal::load(func)? { |
| Literal::Path { |
| associated_to: None, |
| name, |
| } => name, |
| _ => return Err(format!("Unsupported call expression. {:?}", *expr)), |
| }; |
| let mut fields = HashMap::<String, Literal>::default(); |
| for (index, arg) in args.iter().enumerate() { |
| let ident = |
| member_to_ident(&syn::Member::Unnamed(syn::Index::from(index))).to_string(); |
| let value = Literal::load(arg)?; |
| fields.insert(ident, value); |
| } |
| Ok(Literal::Struct { |
| path: Path::new(struct_name.clone()), |
| export_name: struct_name, |
| fields, |
| }) |
| } |
| |
| syn::Expr::Struct(syn::ExprStruct { |
| ref path, |
| ref fields, |
| .. |
| }) => { |
| let struct_name = path.segments[0].ident.unraw().to_string(); |
| let mut field_map = HashMap::<String, Literal>::default(); |
| for field in fields { |
| let ident = member_to_ident(&field.member).to_string(); |
| let value = Literal::load(&field.expr)?; |
| field_map.insert(ident, value); |
| } |
| Ok(Literal::Struct { |
| path: Path::new(struct_name.clone()), |
| export_name: struct_name, |
| fields: field_map, |
| }) |
| } |
| |
| syn::Expr::Unary(syn::ExprUnary { |
| ref op, ref expr, .. |
| }) => match *op { |
| UnOp::Not(_) => { |
| let val = Self::load(expr)?; |
| Ok(Literal::PostfixUnaryOp { |
| op: "~", |
| value: Box::new(val), |
| }) |
| } |
| UnOp::Neg(_) => { |
| let val = Self::load(expr)?; |
| Ok(Literal::PostfixUnaryOp { |
| op: "-", |
| value: Box::new(val), |
| }) |
| } |
| _ => Err(format!("Unsupported Unary expression. {:?}", *op)), |
| }, |
| |
| // Match identifiers, like `5 << SHIFT` |
| syn::Expr::Path(syn::ExprPath { ref path, .. }) => { |
| // Handle only the simplest identifiers and Associated::IDENT |
| // kind of syntax. |
| Ok(match path.segments.len() { |
| 1 => Literal::Path { |
| associated_to: None, |
| name: path.segments[0].ident.to_string(), |
| }, |
| 2 => { |
| let struct_name = path.segments[0].ident.to_string(); |
| Literal::Path { |
| associated_to: Some((Path::new(&struct_name), struct_name)), |
| name: path.segments[1].ident.to_string(), |
| } |
| } |
| _ => return Err(format!("Unsupported path expression. {:?}", path)), |
| }) |
| } |
| |
| syn::Expr::Paren(syn::ExprParen { ref expr, .. }) => Self::load(expr), |
| |
| syn::Expr::Cast(syn::ExprCast { |
| ref expr, ref ty, .. |
| }) => { |
| let val = Self::load(expr)?; |
| match Type::load(ty)? { |
| Some(ty) => Ok(Literal::Cast { |
| ty, |
| value: Box::new(val), |
| }), |
| None => Err("Cannot cast to zero sized type.".to_owned()), |
| } |
| } |
| |
| _ => Err(format!("Unsupported expression. {:?}", *expr)), |
| } |
| } |
| |
| pub(crate) fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { |
| match self { |
| Literal::Expr(v) => match (&**v, config.language) { |
| ("true", Language::Cython) => write!(out, "True"), |
| ("false", Language::Cython) => write!(out, "False"), |
| (v, _) => write!(out, "{}", v), |
| }, |
| Literal::Path { |
| ref associated_to, |
| ref name, |
| } => { |
| if let Some((ref path, ref export_name)) = associated_to { |
| if let Some(known) = to_known_assoc_constant(path, name) { |
| return write!(out, "{}", known); |
| } |
| let path_separator = match config.language { |
| Language::Cython | Language::C => "_", |
| Language::Cxx => { |
| if config.structure.associated_constants_in_body { |
| "::" |
| } else { |
| "_" |
| } |
| } |
| }; |
| write!(out, "{}{}", export_name, path_separator) |
| } |
| write!(out, "{}", name) |
| } |
| Literal::FieldAccess { |
| ref base, |
| ref field, |
| } => { |
| write!(out, "("); |
| base.write(config, out); |
| write!(out, ").{}", field); |
| } |
| Literal::PostfixUnaryOp { op, ref value } => { |
| write!(out, "{}", op); |
| value.write(config, out); |
| } |
| Literal::BinOp { |
| ref left, |
| op, |
| ref right, |
| } => { |
| write!(out, "("); |
| left.write(config, out); |
| write!(out, " {} ", op); |
| right.write(config, out); |
| write!(out, ")"); |
| } |
| Literal::Cast { ref ty, ref value } => { |
| out.write(if config.language == Language::Cython { |
| "<" |
| } else { |
| "(" |
| }); |
| ty.write(config, out); |
| out.write(if config.language == Language::Cython { |
| ">" |
| } else { |
| ")" |
| }); |
| value.write(config, out); |
| } |
| Literal::Struct { |
| export_name, |
| fields, |
| path, |
| } => { |
| match config.language { |
| Language::C => write!(out, "({})", export_name), |
| Language::Cxx => write!(out, "{}", export_name), |
| Language::Cython => write!(out, "<{}>", export_name), |
| } |
| |
| write!(out, "{{ "); |
| let mut is_first_field = true; |
| // In C++, same order as defined is required. |
| let ordered_fields = out.bindings().struct_field_names(path); |
| for ordered_key in ordered_fields.iter() { |
| if let Some(lit) = fields.get(ordered_key) { |
| if !is_first_field { |
| write!(out, ", "); |
| } else { |
| is_first_field = false; |
| } |
| match config.language { |
| Language::Cxx => write!(out, "/* .{} = */ ", ordered_key), |
| Language::C => write!(out, ".{} = ", ordered_key), |
| Language::Cython => {} |
| } |
| lit.write(config, out); |
| } |
| } |
| write!(out, " }}"); |
| } |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| pub struct Constant { |
| pub path: Path, |
| pub export_name: String, |
| pub ty: Type, |
| pub value: Literal, |
| pub cfg: Option<Cfg>, |
| pub annotations: AnnotationSet, |
| pub documentation: Documentation, |
| pub associated_to: Option<Path>, |
| } |
| |
| impl Constant { |
| pub fn load( |
| path: Path, |
| mod_cfg: Option<&Cfg>, |
| ty: &syn::Type, |
| expr: &syn::Expr, |
| attrs: &[syn::Attribute], |
| associated_to: Option<Path>, |
| ) -> Result<Constant, String> { |
| let ty = Type::load(ty)?; |
| let mut ty = match ty { |
| Some(ty) => ty, |
| None => { |
| return Err("Cannot have a zero sized const definition.".to_owned()); |
| } |
| }; |
| |
| let mut lit = Literal::load(expr)?; |
| |
| if let Some(ref associated_to) = associated_to { |
| ty.replace_self_with(associated_to); |
| lit.replace_self_with(associated_to); |
| } |
| |
| Ok(Constant::new( |
| path, |
| ty, |
| lit, |
| Cfg::append(mod_cfg, Cfg::load(attrs)), |
| AnnotationSet::load(attrs)?, |
| Documentation::load(attrs), |
| associated_to, |
| )) |
| } |
| |
| pub fn new( |
| path: Path, |
| ty: Type, |
| value: Literal, |
| cfg: Option<Cfg>, |
| annotations: AnnotationSet, |
| documentation: Documentation, |
| associated_to: Option<Path>, |
| ) -> Self { |
| let export_name = path.name().to_owned(); |
| Self { |
| path, |
| export_name, |
| ty, |
| value, |
| cfg, |
| annotations, |
| documentation, |
| associated_to, |
| } |
| } |
| |
| pub fn uses_only_primitive_types(&self) -> bool { |
| self.value.uses_only_primitive_types() && self.ty.is_primitive_or_ptr_primitive() |
| } |
| } |
| |
| impl Item for Constant { |
| fn path(&self) -> &Path { |
| &self.path |
| } |
| |
| fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { |
| self.ty.add_dependencies(library, out); |
| } |
| |
| fn export_name(&self) -> &str { |
| &self.export_name |
| } |
| |
| fn cfg(&self) -> Option<&Cfg> { |
| self.cfg.as_ref() |
| } |
| |
| fn annotations(&self) -> &AnnotationSet { |
| &self.annotations |
| } |
| |
| fn annotations_mut(&mut self) -> &mut AnnotationSet { |
| &mut self.annotations |
| } |
| |
| fn container(&self) -> ItemContainer { |
| ItemContainer::Constant(self.clone()) |
| } |
| |
| fn rename_for_config(&mut self, config: &Config) { |
| if self.associated_to.is_none() { |
| config.export.rename(&mut self.export_name); |
| } |
| self.value.rename_for_config(config); |
| self.ty.rename_for_config(config, &GenericParams::default()); // FIXME: should probably propagate something here |
| } |
| |
| fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { |
| self.ty.resolve_declaration_types(resolver); |
| } |
| } |
| |
| impl Constant { |
| pub fn write_declaration<F: Write>( |
| &self, |
| config: &Config, |
| out: &mut SourceWriter<F>, |
| associated_to_struct: &Struct, |
| ) { |
| debug_assert!(self.associated_to.is_some()); |
| debug_assert!(config.language == Language::Cxx); |
| debug_assert!(!associated_to_struct.is_transparent); |
| debug_assert!(config.structure.associated_constants_in_body); |
| debug_assert!(config.constant.allow_static_const); |
| |
| if let Type::Ptr { is_const: true, .. } = self.ty { |
| out.write("static "); |
| } else { |
| out.write("static const "); |
| } |
| self.ty.write(config, out); |
| write!(out, " {};", self.export_name()) |
| } |
| |
| pub fn write<F: Write>( |
| &self, |
| config: &Config, |
| out: &mut SourceWriter<F>, |
| associated_to_struct: Option<&Struct>, |
| ) { |
| if let Some(assoc) = associated_to_struct { |
| if assoc.is_generic() { |
| return; // Not tested / implemented yet, so bail out. |
| } |
| } |
| |
| if !self.value.is_valid(out.bindings()) { |
| return; |
| } |
| |
| let associated_to_transparent = associated_to_struct.map_or(false, |s| s.is_transparent); |
| |
| let in_body = associated_to_struct.is_some() |
| && config.language == Language::Cxx |
| && config.structure.associated_constants_in_body |
| && config.constant.allow_static_const |
| && !associated_to_transparent; |
| |
| let condition = self.cfg.to_condition(config); |
| condition.write_before(config, out); |
| |
| let name = if in_body { |
| Cow::Owned(format!( |
| "{}::{}", |
| associated_to_struct.unwrap().export_name(), |
| self.export_name(), |
| )) |
| } else if self.associated_to.is_none() { |
| Cow::Borrowed(self.export_name()) |
| } else { |
| let associated_name = match associated_to_struct { |
| Some(s) => Cow::Borrowed(s.export_name()), |
| None => { |
| let mut name = self.associated_to.as_ref().unwrap().name().to_owned(); |
| config.export.rename(&mut name); |
| Cow::Owned(name) |
| } |
| }; |
| |
| Cow::Owned(format!("{}_{}", associated_name, self.export_name())) |
| }; |
| |
| let value = match self.value { |
| Literal::Struct { |
| ref fields, |
| ref path, |
| .. |
| } if out.bindings().struct_is_transparent(path) => fields.iter().next().unwrap().1, |
| _ => &self.value, |
| }; |
| |
| self.documentation.write(config, out); |
| |
| let allow_constexpr = config.constant.allow_constexpr && self.value.can_be_constexpr(); |
| match config.language { |
| Language::Cxx if config.constant.allow_static_const || allow_constexpr => { |
| if allow_constexpr { |
| out.write("constexpr ") |
| } |
| |
| if config.constant.allow_static_const { |
| out.write(if in_body { "inline " } else { "static " }); |
| } |
| |
| if let Type::Ptr { is_const: true, .. } = self.ty { |
| // Nothing. |
| } else { |
| out.write("const "); |
| } |
| |
| self.ty.write(config, out); |
| write!(out, " {} = ", name); |
| value.write(config, out); |
| write!(out, ";"); |
| } |
| Language::Cxx | Language::C => { |
| write!(out, "#define {} ", name); |
| value.write(config, out); |
| } |
| Language::Cython => { |
| out.write("const "); |
| self.ty.write(config, out); |
| // For extern Cython declarations the initializer is ignored, |
| // but still useful as documentation, so we write it as a comment. |
| write!(out, " {} # = ", name); |
| value.write(config, out); |
| } |
| } |
| |
| condition.write_after(config, out); |
| } |
| } |