diff options
Diffstat (limited to 'src/bindgen/writer.rs')
-rw-r--r-- | src/bindgen/writer.rs | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/src/bindgen/writer.rs b/src/bindgen/writer.rs new file mode 100644 index 0000000..eed6917 --- /dev/null +++ b/src/bindgen/writer.rs @@ -0,0 +1,258 @@ +/* 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<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: 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<S: Source>( + &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<S: Source>(&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<F: Write>(&self, config: &Config, _: &mut SourceWriter<F>); +} |