| /* 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, Language}; |
| use crate::bindgen::language_backend::LanguageBackend; |
| 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<usize> { |
| 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<usize>, |
| line_started: bool, |
| line_length: usize, |
| line_number: usize, |
| max_line_length: usize, |
| } |
| |
| pub type MeasureWriter<'a> = SourceWriter<'a, &'a mut Vec<u8>>; |
| |
| 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<T>(&mut self, func: T, max_line_length: usize) -> bool |
| where |
| T: FnOnce(&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< |
| LB: LanguageBackend, |
| S, |
| WF: Fn(&mut LB, &mut SourceWriter<F>, &S), |
| >( |
| &mut self, |
| language_backend: &mut LB, |
| items: &[S], |
| list_type: ListType<'_>, |
| writer: WF, |
| ) { |
| for (i, item) in items.iter().enumerate() { |
| writer(language_backend, self, item); |
| |
| 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< |
| LB: LanguageBackend, |
| S, |
| WF: Fn(&mut LB, &mut SourceWriter<F>, &S), |
| >( |
| &mut self, |
| language_backend: &mut LB, |
| items: &[S], |
| list_type: ListType<'_>, |
| writer: WF, |
| ) { |
| let align_length = self.line_length_for_align(); |
| self.push_set_spaces(align_length); |
| for (i, item) in items.iter().enumerate() { |
| writer(language_backend, self, item); |
| |
| 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(); |
| } |
| } |