Extract Rust2Js like Js2Rust was extracted

Along the way clean up a lot of the formatting of the auto-generated code to
make it a bit prettier by default.
This commit is contained in:
Alex Crichton 2018-04-16 13:31:56 -07:00
parent 49ead6a0d0
commit efb64b87d4
3 changed files with 437 additions and 304 deletions

View File

@ -1,4 +1,4 @@
use super::Context;
use super::{indent, Context};
use descriptor::{Descriptor, Function};
/// Helper struct for manfuacturing a shim in JS used to translate JS types to
@ -305,13 +305,3 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
ret
}
}
fn indent(s: &str) -> String {
let mut ret = String::new();
for line in s.lines() {
ret.push_str(" ");
ret.push_str(line);
ret.push_str("\n");
}
return ret
}

View File

@ -12,6 +12,8 @@ use descriptor::{Descriptor, VectorKind};
mod js2rust;
use self::js2rust::Js2Rust;
mod rust2js;
use self::rust2js::Rust2Js;
pub struct Context<'a> {
pub globals: String,
@ -42,6 +44,7 @@ pub struct SubContext<'a, 'b: 'a> {
impl<'a> Context<'a> {
fn export(&mut self, name: &str, contents: &str) {
let contents = deindent(contents);
let contents = contents.trim();
let global = if self.config.nodejs {
format!("module.exports.{} = {};\n", name, contents)
@ -49,7 +52,7 @@ impl<'a> Context<'a> {
format!("__exports.{} = {}\n", name, contents)
} else {
if contents.starts_with("function") {
format!("export function {} {}\n", name, &contents[8..])
format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") {
format!("export {}\n", contents)
} else {
@ -69,8 +72,7 @@ impl<'a> Context<'a> {
return;
}
let contents = f(self);
let contents = contents.trim();
self.export(name, contents);
self.export(name, &contents);
};
bind("__wbindgen_object_clone_ref", &|me| {
@ -108,9 +110,11 @@ impl<'a> Context<'a> {
bind("__wbindgen_string_new", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
String::from("function(p, l) {
return addHeapObject(getStringFromWasm(p, l));
}")
String::from("
function(p, l) {
return addHeapObject(getStringFromWasm(p, l));
}
")
});
bind("__wbindgen_number_new", &|me| {
@ -139,64 +143,78 @@ impl<'a> Context<'a> {
bind("__wbindgen_null_new", &|me| {
me.expose_add_heap_object();
String::from("function() {
return addHeapObject(null);
}")
String::from("
function() {
return addHeapObject(null);
}
")
});
bind("__wbindgen_is_null", &|me| {
me.expose_get_object();
String::from("function(idx) {
return getObject(idx) === null ? 1 : 0;
}")
String::from("
function(idx) {
return getObject(idx) === null ? 1 : 0;
}
")
});
bind("__wbindgen_is_undefined", &|me| {
me.expose_get_object();
String::from("function(idx) {
return getObject(idx) === undefined ? 1 : 0;
}")
String::from("
function(idx) {
return getObject(idx) === undefined ? 1 : 0;
}
")
});
bind("__wbindgen_boolean_new", &|me| {
me.expose_add_heap_object();
String::from("function(v) {
return addHeapObject(v === 1);
}")
String::from("
function(v) {
return addHeapObject(v === 1);
}
")
});
bind("__wbindgen_boolean_get", &|me| {
me.expose_get_object();
String::from("function(i) {
let v = getObject(i);
if (typeof(v) === 'boolean') {
return v ? 1 : 0;
} else {
return 2;
String::from("
function(i) {
let v = getObject(i);
if (typeof(v) === 'boolean') {
return v ? 1 : 0;
} else {
return 2;
}
}
}")
")
});
bind("__wbindgen_symbol_new", &|me| {
me.expose_get_string_from_wasm();
me.expose_add_heap_object();
format!("function(ptr, len) {{
let a;
console.log(ptr, len);
if (ptr === 0) {{
a = Symbol();
}} else {{
a = Symbol(getStringFromWasm(ptr, len));
format!("
function(ptr, len) {{
let a;
console.log(ptr, len);
if (ptr === 0) {{
a = Symbol();
}} else {{
a = Symbol(getStringFromWasm(ptr, len));
}}
return addHeapObject(a);
}}
return addHeapObject(a);
}}")
")
});
bind("__wbindgen_is_symbol", &|me| {
me.expose_get_object();
String::from("function(i) {
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
}")
String::from("
function(i) {
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
}
")
});
bind("__wbindgen_throw", &|me| {
@ -212,51 +230,35 @@ impl<'a> Context<'a> {
me.expose_pass_string_to_wasm();
me.expose_get_object();
me.expose_uint32_memory();
String::from("function(i, len_ptr) {
let obj = getObject(i);
if (typeof(obj) !== 'string')
return 0;
const [ptr, len] = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = len;
return ptr;
}")
String::from("
function(i, len_ptr) {
let obj = getObject(i);
if (typeof(obj) !== 'string')
return 0;
const [ptr, len] = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = len;
return ptr;
}
")
});
for i in 0..8 {
let name = format!("__wbindgen_cb_arity{}", i);
bind(&name, &|me| {
me.expose_add_heap_object();
me.function_table_needed = true;
let args = (0..i)
.map(|x| format!("arg{}", x))
.collect::<Vec<_>>()
.join(", ");
format!("function(a, b, c) {{
const cb = function({0}) {{
return this.f(this.a, this.b {1} {0});
}};
cb.a = b;
cb.b = c;
cb.f = wasm.__wbg_function_table.get(a);
let real = cb.bind(cb);
real.original = cb;
return addHeapObject(real);
}}", args, if i == 0 {""} else {","})
});
}
bind("__wbindgen_cb_drop", &|me| {
me.expose_drop_ref();
String::from("function(i) {
let obj = getObject(i).original;
obj.a = obj.b = 0;
dropRef(i);
}")
String::from("
function(i) {
let obj = getObject(i).original;
obj.a = obj.b = 0;
dropRef(i);
}
")
});
bind("__wbindgen_cb_forget", &|me| {
me.expose_drop_ref();
String::from("function(i) {
dropRef(i);
}")
String::from("
function(i) {
dropRef(i);
}
")
});
}
@ -1222,49 +1224,17 @@ impl<'a> Context<'a> {
Descriptor::decode(&ret)
}
fn return_from_js(&mut self, ty: &Option<Descriptor>, invoc: &str) -> String {
let ty = match *ty {
Some(ref t) => t,
None => return invoc.to_string(),
};
if ty.is_by_ref() {
panic!("cannot return a reference from JS to Rust")
}
if let Some(ty) = ty.vector_kind() {
let f = self.pass_to_wasm_function(ty);
self.expose_uint32_memory();
self.expose_set_global_argument();
return format!("
const [retptr, retlen] = {}({});
setGlobalArgument(retlen, 0);
return retptr;
", f, invoc)
}
if ty.is_number() {
return format!("return {};", invoc)
}
match *ty {
Descriptor::Boolean => format!("return {} ? 1 : 0;", invoc),
Descriptor::Anyref => {
self.expose_add_heap_object();
format!("return addHeapObject({});", invoc)
}
_ => panic!("unimplemented return from JS to Rust: {:?}", ty),
}
}
fn global(&mut self, s: &str) {
let amt_to_strip = s.lines()
.filter(|l| !l.trim().is_empty())
.map(|s| s.len() - s.trim_left().len())
.min()
.unwrap_or(0);
for line in s.lines() {
if !line.trim().is_empty() {
self.globals.push_str(&line[amt_to_strip..]);
}
let s = deindent(s);
let s = s.trim();
// Ensure a blank line between adjacent items, and ensure everything is
// terminated with a newline.
while !self.globals.ends_with("\n\n\n") {
self.globals.push_str("\n");
}
self.globals.push_str(s);
self.globals.push_str("\n");
}
}
@ -1363,142 +1333,8 @@ impl<'a, 'b> SubContext<'a, 'b> {
info: &shared::Import,
import: &shared::ImportFunction) {
let descriptor = self.cx.describe(&import.shim);
let desc_function = descriptor.unwrap_function();
let mut dst = String::new();
dst.push_str("function(");
let mut invoc_args = Vec::new();
let mut abi_args = Vec::new();
let mut extra = String::new();
let mut finally = String::new();
let mut next_global = 0;
for (i, arg) in desc_function.arguments.iter().enumerate() {
abi_args.push(format!("arg{}", i));
if let Some(ty) = arg.vector_kind() {
let f = self.cx.expose_get_vector_from_wasm(ty);
self.cx.expose_get_global_argument();
extra.push_str(&format!("
let len{0} = getGlobalArgument({next_global});
let v{0} = {func}(arg{0}, len{0});
", i, func = f, next_global = next_global));
next_global += 1;
if !arg.is_by_ref() {
extra.push_str(&format!("
wasm.__wbindgen_free(arg{0}, len{0} * {size});
", i, size = ty.size()));
self.cx.required_internal_exports.insert(
"__wbindgen_free"
);
}
invoc_args.push(format!("v{}", i));
continue
}
if let Some(class) = arg.rust_struct() {
if arg.is_by_ref() {
panic!("cannot invoke JS functions with custom ref types yet")
}
let assign = format!("let c{0} = {1}.__construct(arg{0});", i, class);
extra.push_str(&assign);
invoc_args.push(format!("c{}", i));
continue
}
if let Some((f, mutable)) = arg.stack_closure() {
let (js, _ts) = {
let mut builder = Js2Rust::new("", self.cx);
if mutable {
builder.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.rust_argument("this.b")
.process(f)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.function_table_needed = true;
extra.push_str(&format!("
let cb{0} = {js};
cb{0}.f = wasm.__wbg_function_table.get(arg{0});
cb{0}.a = getGlobalArgument({next_global});
cb{0}.b = getGlobalArgument({next_global} + 1);
", i, js = js, next_global = next_global));
next_global += 2;
finally.push_str(&format!("
cb{0}.a = cb{0}.b = 0;
", i));
invoc_args.push(format!("cb{0}.bind(cb{0})", i));
continue
}
if let Some((f, mutable)) = arg.ref_closure() {
let (js, _ts) = {
let mut builder = Js2Rust::new("", self.cx);
if mutable {
builder.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.rust_argument("this.b")
.process(f)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
self.cx.function_table_needed = true;
extra.push_str(&format!("
let idx{0} = getUint32Memory()[arg{0} / 4];
if (idx{0} === 0xffffffff) {{
let cb{0} = {js};
cb{0}.a = getGlobalArgument({next_global});
cb{0}.b = getGlobalArgument({next_global} + 1);
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({next_global} + 2));
let real = cb{0}.bind(cb{0});
real.original = cb{0};
idx{0} = getUint32Memory()[arg{0} / 4] = addHeapObject(real);
}}
", i, js = js, next_global = next_global));
next_global += 3;
self.cx.expose_get_object();
invoc_args.push(format!("getObject(idx{})", i));
continue
}
let invoc_arg = match *arg {
ref d if d.is_number() => format!("arg{}", i),
Descriptor::Boolean => format!("arg{} !== 0", i),
Descriptor::Anyref => {
self.cx.expose_take_object();
format!("takeObject(arg{})", i)
}
ref d if d.is_ref_anyref() => {
self.cx.expose_get_object();
format!("getObject(arg{})", i)
}
_ => panic!("unimplemented argument type in imported function: {:?}", arg),
};
invoc_args.push(invoc_arg);
}
let nargs = invoc_args.len();
let invoc_args = invoc_args.join(", ");
let function_name = &import.function.name;
let invoc = match import.class {
let target = match import.class {
Some(ref class) if import.js_new => {
format!("new {}", self.import_name(info, class))
}
@ -1530,6 +1366,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
} else {
if import.structural {
let nargs = descriptor.unwrap_function().arguments.len();
let mut s = format!("function(");
for i in 0..nargs - 1 {
if i > 0 {
@ -1538,7 +1375,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
drop(write!(s, "x{}", i));
}
s.push_str(") { return this.");
s.push_str(function_name);
s.push_str(&import.function.name);
s.push_str("(");
for i in 0..nargs - 1 {
if i > 0 {
@ -1549,7 +1386,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
s.push_str("); }");
s
} else {
format!("{}.prototype.{}", class, function_name)
format!("{}.prototype.{}", class, import.function.name)
}
};
self.cx.global(&format!("
@ -1561,11 +1398,11 @@ impl<'a, 'b> SubContext<'a, 'b> {
let class = self.import_name(info, class);
self.cx.global(&format!("
const {}_target = {}.{};
", import.shim, class, function_name));
", import.shim, class, import.function.name));
format!("{}_target", import.shim)
}
None => {
let name = self.import_name(info, function_name);
let name = self.import_name(info, &import.function.name);
if name.contains(".") {
self.cx.global(&format!("
const {}_target = {};
@ -1576,42 +1413,12 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
}
};
let invoc = format!("{}({})", invoc, invoc_args);
let invoc = self.cx.return_from_js(&desc_function.ret, &invoc);
let invoc = if import.catch {
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
abi_args.push("exnptr".to_string());
format!("
try {{
{}
}} catch (e) {{
const view = getUint32Memory();
view[exnptr / 4] = 1;
view[exnptr / 4 + 1] = addHeapObject(e);
}}
", invoc)
} else {
invoc
};
let invoc = if finally.len() > 0 {
format!("
try {{
{}
}} finally {{
{}
}}
", invoc, finally)
} else {
invoc
};
dst.push_str(&abi_args.join(", "));
dst.push_str(") {\n");
dst.push_str(&extra);
dst.push_str(&format!("{}\n}}", invoc));
self.cx.export(&import.shim, &dst);
let js = Rust2Js::new(self.cx)
.catch(import.catch)
.process(descriptor.unwrap_function())
.finish(&target);
self.cx.export(&import.shim, &js);
}
pub fn generate_enum(&mut self, enum_: &shared::Enum) {
@ -1641,12 +1448,12 @@ impl<'a, 'b> SubContext<'a, 'b> {
if self.cx.imported_names.insert(name.to_string()) {
if self.cx.config.nodejs {
self.cx.imports.push_str(&format!("
const {} = require('{}').{};
self.cx.imports.push_str(&format!("\
const {} = require('{}').{};\n\
", name, module, name));
} else {
self.cx.imports.push_str(&format!("
import {{ {} }} from '{}';
self.cx.imports.push_str(&format!("\
import {{ {} }} from '{}';\n\
", name, module));
}
}
@ -1657,3 +1464,29 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
}
}
fn indent(s: &str) -> String {
let mut ret = String::new();
for line in s.lines() {
ret.push_str(" ");
ret.push_str(line);
ret.push_str("\n");
}
return ret
}
fn deindent(s: &str) -> String {
let amt_to_strip = s.lines()
.filter(|l| !l.trim().is_empty())
.map(|s| s.len() - s.trim_left().len())
.min()
.unwrap_or(0);
let mut ret = String::new();
for line in s.lines() {
if !line.trim().is_empty() {
ret.push_str(&line[amt_to_strip..]);
}
ret.push_str("\n");
}
ret
}

View File

@ -0,0 +1,310 @@
use super::Context;
use descriptor::{Descriptor, Function};
use super::{indent, Js2Rust};
/// Helper struct for manfuacturing a shim in JS used to translate Rust types to
/// JS, then invoking an imported JS function.
pub struct Rust2Js<'a, 'b: 'a> {
cx: &'a mut Context<'b>,
/// Arguments of the JS shim that we're generating, aka the variables passed
/// from Rust which are only numbers.
shim_arguments: Vec<String>,
/// Arguments which are forwarded to the imported JS function
js_arguments: Vec<String>,
/// Conversions that happen before we invoke the wasm function, such as
/// converting a string to a ptr/length pair.
prelude: String,
/// "Destructors" or cleanup that must happen after the wasm function
/// finishes. This is scheduled in a `finally` block.
finally: String,
/// Next global index to write to when passing arguments via the single
/// global stack.
global_idx: usize,
/// Index of the next argument for unique name generation purposes.
arg_idx: usize,
/// Expression used to generate the return value. The string "JS" in this
/// expression is replaced with the actual JS invocation eventually.
ret_expr: String,
/// Whether or not we're catching JS exceptions
catch: bool,
}
impl<'a, 'b> Rust2Js<'a, 'b> {
pub fn new(cx: &'a mut Context<'b>) -> Rust2Js<'a, 'b> {
Rust2Js {
cx,
shim_arguments: Vec::new(),
js_arguments: Vec::new(),
prelude: String::new(),
finally: String::new(),
global_idx: 0,
arg_idx: 0,
ret_expr: String::new(),
catch: false,
}
}
pub fn catch(&mut self, catch: bool) -> &mut Self {
if catch {
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
}
self.catch = catch;
self
}
/// Generates all bindings necessary for the signature in `Function`,
/// creating necessary argument conversions and return value processing.
pub fn process(&mut self, function: &Function) -> &mut Self {
for arg in function.arguments.iter() {
self.argument(arg);
}
self.ret(&function.ret);
self
}
fn argument(&mut self, arg: &Descriptor) {
let i = self.arg_idx;
self.arg_idx += 1;
self.shim_arguments.push(format!("arg{}", i));
if let Some(ty) = arg.vector_kind() {
let f = self.cx.expose_get_vector_from_wasm(ty);
self.cx.expose_get_global_argument();
let next_global = self.global_idx();
self.prelude(&format!("\
let len{0} = getGlobalArgument({next_global});\n\
let v{0} = {func}(arg{0}, len{0});\n\
", i, func = f, next_global = next_global));
if !arg.is_by_ref() {
self.prelude(&format!("\
wasm.__wbindgen_free(arg{0}, len{0} * {size});\
", i, size = ty.size()));
self.cx.required_internal_exports.insert(
"__wbindgen_free"
);
}
self.js_arguments.push(format!("v{}", i));
return
}
if let Some(class) = arg.rust_struct() {
if arg.is_by_ref() {
panic!("cannot invoke JS functions with custom ref types yet")
}
let assign = format!("let c{0} = {1}.__construct(arg{0});", i, class);
self.prelude(&assign);
self.js_arguments.push(format!("c{}", i));
return
}
if let Some((f, mutable)) = arg.stack_closure() {
let (js, _ts) = {
let mut builder = Js2Rust::new("", self.cx);
if mutable {
builder.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.rust_argument("this.b")
.process(f)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.function_table_needed = true;
let next_global = self.global_idx();
self.global_idx();
self.prelude(&format!("\
let cb{0} = {js};\n\
cb{0}.f = wasm.__wbg_function_table.get(arg{0});\n\
cb{0}.a = getGlobalArgument({next_global});\n\
cb{0}.b = getGlobalArgument({next_global} + 1);\n\
", i, js = js, next_global = next_global));
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", i));
self.js_arguments.push(format!("cb{0}.bind(cb{0})", i));
return
}
if let Some((f, mutable)) = arg.ref_closure() {
let (js, _ts) = {
let mut builder = Js2Rust::new("", self.cx);
if mutable {
builder.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.rust_argument("this.b")
.process(f)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
self.cx.function_table_needed = true;
let reset_idx = format!("\
let cb{0} = {js};\n\
cb{0}.a = getGlobalArgument({a});\n\
cb{0}.b = getGlobalArgument({b});\n\
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({c}));\n\
let real = cb{0}.bind(cb{0});\n\
real.original = cb{0};\n\
idx{0} = getUint32Memory()[arg{0} / 4] = addHeapObject(real);\n\
",
i,
js = js,
a = self.global_idx(),
b = self.global_idx(),
c = self.global_idx(),
);
self.prelude(&format!("\
let idx{0} = getUint32Memory()[arg{0} / 4];\n\
if (idx{0} === 0xffffffff) {{\n\
{1}\
}}\n\
", i, indent(&reset_idx)));
self.cx.expose_get_object();
self.js_arguments.push(format!("getObject(idx{})", i));
return
}
let invoc_arg = match *arg {
ref d if d.is_number() => format!("arg{}", i),
Descriptor::Boolean => format!("arg{} !== 0", i),
Descriptor::Anyref => {
self.cx.expose_take_object();
format!("takeObject(arg{})", i)
}
ref d if d.is_ref_anyref() => {
self.cx.expose_get_object();
format!("getObject(arg{})", i)
}
_ => panic!("unimplemented argument type in imported function: {:?}", arg),
};
self.js_arguments.push(invoc_arg);
}
fn ret(&mut self, ret: &Option<Descriptor>) {
let ty = match *ret {
Some(ref t) => t,
None => {
self.ret_expr = "JS;".to_string();
return
}
};
if ty.is_by_ref() {
panic!("cannot return a reference from JS to Rust")
}
if let Some(ty) = ty.vector_kind() {
let f = self.cx.pass_to_wasm_function(ty);
self.cx.expose_uint32_memory();
self.cx.expose_set_global_argument();
self.ret_expr = format!("\
const [retptr, retlen] = {}(JS);\n\
setGlobalArgument(retlen, 0);\n\
return retptr;\n\
", f);
return
}
if ty.is_number() {
self.ret_expr = "return JS;".to_string();
return
}
self.ret_expr = match *ty {
Descriptor::Boolean => "return JS ? 1 : 0;".to_string(),
Descriptor::Anyref => {
self.cx.expose_add_heap_object();
"return addHeapObject(JS);".to_string()
}
_ => panic!("unimplemented return from JS to Rust: {:?}", ty),
}
}
pub fn finish(&self, invoc: &str) -> String {
let mut ret = String::new();
ret.push_str("function(");
ret.push_str(&self.shim_arguments.join(", "));
if self.catch {
if self.shim_arguments.len() > 0 {
ret.push_str(", ")
}
ret.push_str("exnptr");
}
ret.push_str(") {\n");
ret.push_str(&indent(&self.prelude));
let mut invoc = self.ret_expr.replace(
"JS",
&format!("{}({})", invoc, self.js_arguments.join(", ")),
);
if self.catch {
let catch = "\
const view = getUint32Memory();\n\
view[exnptr / 4] = 1;\n\
view[exnptr / 4 + 1] = addHeapObject(e);\n\
";
invoc = format!("\
try {{\n\
{}\
}} catch (e) {{\n\
{}\
}}\
", indent(&invoc), indent(catch));
};
if self.finally.len() > 0 {
invoc = format!("\
try {{\n\
{}\
}} finally {{\n\
{}\
}}\
", indent(&invoc), indent(&self.finally));
}
ret.push_str(&indent(&invoc));
ret.push_str("}\n");
return ret
}
fn global_idx(&mut self) -> usize {
let ret = self.global_idx;
self.global_idx += 1;
ret
}
fn prelude(&mut self, s: &str) -> &mut Self {
for line in s.lines() {
self.prelude.push_str(line);
self.prelude.push_str("\n");
}
self
}
fn finally(&mut self, s: &str) -> &mut Self {
for line in s.lines() {
self.finally.push_str(line);
self.finally.push_str("\n");
}
self
}
}