/* 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::cmp; use std::io; use std::io::Write; use crate::bindgen::config::{Braces, Config, Language}; use crate::bindgen::Bindings; /// A type of way to format a list. pub enum ListType<'a> { /// Join each adjacent item with a str. Join(&'a str), /// End each item with a str. Cap(&'a str), } /// A utility wrapper to write unbuffered data and correctly adjust positions. struct InnerWriter<'a, 'b: 'a, F: 'a + Write>(&'a mut SourceWriter<'b, F>); impl<'a, 'b, F: Write> Write for InnerWriter<'a, 'b, F> { fn write(&mut self, buf: &[u8]) -> io::Result { let writer = &mut self.0; if !writer.line_started { for _ in 0..writer.spaces() { write!(writer.out, " ").unwrap(); } writer.line_started = true; writer.line_length += writer.spaces(); } let written = writer.out.write(buf)?; writer.line_length += written; writer.max_line_length = cmp::max(writer.max_line_length, writer.line_length); Ok(written) } fn flush(&mut self) -> io::Result<()> { self.0.out.flush() } } /// A utility writer for generating code easier. pub struct SourceWriter<'a, F: Write> { out: F, bindings: &'a Bindings, spaces: Vec, line_started: bool, line_length: usize, line_number: usize, max_line_length: usize, } pub type MeasureWriter<'a> = SourceWriter<'a, &'a mut Vec>; impl<'a, F: Write> SourceWriter<'a, F> { pub fn new(out: F, bindings: &'a Bindings) -> Self { SourceWriter { out, bindings, spaces: vec![0], line_started: false, line_length: 0, line_number: 1, max_line_length: 0, } } pub fn bindings(&self) -> &Bindings { self.bindings } /// Takes a function that writes source and returns the maximum line length /// written. pub fn try_write(&mut self, func: T, max_line_length: usize) -> bool where T: Fn(&mut MeasureWriter), { if self.line_length > max_line_length { return false; } let mut buffer = Vec::new(); let line_length = { let mut measurer = SourceWriter { out: &mut buffer, bindings: self.bindings, spaces: self.spaces.clone(), line_started: self.line_started, line_length: self.line_length, line_number: self.line_number, max_line_length: self.line_length, }; func(&mut measurer); measurer.max_line_length }; if line_length > max_line_length { return false; } // We don't want the extra alignment, it's already accounted for by the // measurer. self.line_started = true; InnerWriter(self).write_all(&buffer).unwrap(); true } fn spaces(&self) -> usize { *self.spaces.last().unwrap() } pub fn push_set_spaces(&mut self, spaces: usize) { self.spaces.push(spaces); } pub fn pop_set_spaces(&mut self) { self.pop_tab() } pub fn line_length_for_align(&self) -> usize { if self.line_started { self.line_length } else { self.line_length + self.spaces() } } pub fn push_tab(&mut self) { let spaces = self.spaces() - (self.spaces() % self.bindings.config.tab_width) + self.bindings.config.tab_width; self.spaces.push(spaces); } pub fn pop_tab(&mut self) { assert!(!self.spaces.is_empty()); self.spaces.pop(); } pub fn new_line(&mut self) { self.out .write_all(self.bindings.config.line_endings.as_str().as_bytes()) .unwrap(); self.line_started = false; self.line_length = 0; self.line_number += 1; } pub fn new_line_if_not_start(&mut self) { if self.line_number != 1 { self.new_line(); } } pub fn open_brace(&mut self) { match self.bindings.config.language { Language::Cxx | Language::C => match self.bindings.config.braces { Braces::SameLine => { self.write(" {"); self.push_tab(); self.new_line(); } Braces::NextLine => { self.new_line(); self.write("{"); self.push_tab(); self.new_line(); } }, Language::Cython => { self.write(":"); self.new_line(); self.push_tab(); } } } pub fn close_brace(&mut self, semicolon: bool) { self.pop_tab(); match self.bindings.config.language { Language::Cxx | Language::C => { self.new_line(); if semicolon { self.write("};"); } else { self.write("}"); } } Language::Cython => {} } } pub fn write(&mut self, text: &'static str) { write!(self, "{}", text); } pub fn write_raw_block(&mut self, block: &str) { self.line_started = true; write!(self, "{}", block); } pub fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) { InnerWriter(self).write_fmt(fmt).unwrap(); } pub fn write_horizontal_source_list( &mut self, items: &[S], list_type: ListType<'_>, ) { for (i, item) in items.iter().enumerate() { item.write(&self.bindings.config, self); match list_type { ListType::Join(text) => { if i != items.len() - 1 { write!(self, "{}", text); } } ListType::Cap(text) => { write!(self, "{}", text); } } } } pub fn write_vertical_source_list(&mut self, items: &[S], list_type: ListType<'_>) { let align_length = self.line_length_for_align(); self.push_set_spaces(align_length); for (i, item) in items.iter().enumerate() { item.write(&self.bindings.config, self); match list_type { ListType::Join(text) => { if i != items.len() - 1 { write!(self, "{}", text); } } ListType::Cap(text) => { write!(self, "{}", text); } } if i != items.len() - 1 { self.new_line(); } } self.pop_tab(); } } pub trait Source { fn write(&self, config: &Config, _: &mut SourceWriter); }