summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/smoosh/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend/smoosh/src/lib.rs')
-rw-r--r--js/src/frontend/smoosh/src/lib.rs769
1 files changed, 769 insertions, 0 deletions
diff --git a/js/src/frontend/smoosh/src/lib.rs b/js/src/frontend/smoosh/src/lib.rs
new file mode 100644
index 0000000000..f663706533
--- /dev/null
+++ b/js/src/frontend/smoosh/src/lib.rs
@@ -0,0 +1,769 @@
+/* Copyright Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0, or the MIT license,
+ * (the "Licenses") at your option. You may not use this file except in
+ * compliance with one of the Licenses. You may obtain copies of the
+ * Licenses at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licenses for the specific language governing permissions and
+ * limitations under the Licenses.
+ */
+
+use bumpalo;
+use env_logger;
+use jsparagus::ast::source_atom_set::SourceAtomSet;
+use jsparagus::ast::source_slice_list::SourceSliceList;
+use jsparagus::ast::types::Program;
+use jsparagus::emitter::{emit, EmitError, EmitOptions};
+use jsparagus::parser::{parse_module, parse_script, ParseError, ParseOptions};
+use jsparagus::stencil::gcthings::GCThing;
+use jsparagus::stencil::regexp::RegExpItem;
+use jsparagus::stencil::result::EmitResult;
+use jsparagus::stencil::scope::{BindingName, ScopeData};
+use jsparagus::stencil::scope_notes::ScopeNote;
+use jsparagus::stencil::script::{ImmutableScriptData, ScriptStencil, SourceExtent};
+use std::boxed::Box;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::os::raw::{c_char, c_void};
+use std::rc::Rc;
+use std::{mem, slice, str};
+
+#[repr(C)]
+pub struct CVec<T> {
+ pub data: *mut T,
+ pub len: usize,
+ pub capacity: usize,
+}
+
+impl<T> CVec<T> {
+ fn empty() -> CVec<T> {
+ Self {
+ data: std::ptr::null_mut(),
+ len: 0,
+ capacity: 0,
+ }
+ }
+
+ fn from(mut v: Vec<T>) -> CVec<T> {
+ let result = Self {
+ data: v.as_mut_ptr(),
+ len: v.len(),
+ capacity: v.capacity(),
+ };
+ mem::forget(v);
+ result
+ }
+
+ unsafe fn into(self) -> Vec<T> {
+ Vec::from_raw_parts(self.data, self.len, self.capacity)
+ }
+}
+
+#[repr(C)]
+pub struct SmooshCompileOptions {
+ no_script_rval: bool,
+}
+
+#[repr(C, u8)]
+pub enum SmooshGCThing {
+ Null,
+ Atom(usize),
+ Function(usize),
+ Scope(usize),
+ RegExp(usize),
+}
+
+fn convert_gcthing(item: GCThing, scope_index_map: &HashMap<usize, usize>) -> SmooshGCThing {
+ match item {
+ GCThing::Null => SmooshGCThing::Null,
+ GCThing::Atom(index) => SmooshGCThing::Atom(index.into()),
+ GCThing::Function(index) => SmooshGCThing::Function(index.into()),
+ GCThing::RegExp(index) => SmooshGCThing::RegExp(index.into()),
+ GCThing::Scope(index) => {
+ let mapped_index = *scope_index_map
+ .get(&index.into())
+ .expect("Scope map should be populated");
+ SmooshGCThing::Scope(mapped_index)
+ }
+ }
+}
+
+#[repr(C)]
+pub struct SmooshBindingName {
+ name: usize,
+ is_closed_over: bool,
+ is_top_level_function: bool,
+}
+
+impl From<BindingName> for SmooshBindingName {
+ fn from(info: BindingName) -> Self {
+ Self {
+ name: info.name.into(),
+ is_closed_over: info.is_closed_over,
+ is_top_level_function: info.is_top_level_function,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct SmooshGlobalScopeData {
+ bindings: CVec<SmooshBindingName>,
+ let_start: usize,
+ const_start: usize,
+}
+
+#[repr(C)]
+pub struct SmooshVarScopeData {
+ bindings: CVec<SmooshBindingName>,
+ enclosing: usize,
+ function_has_extensible_scope: bool,
+ first_frame_slot: u32,
+}
+
+#[repr(C)]
+pub struct SmooshLexicalScopeData {
+ bindings: CVec<SmooshBindingName>,
+ const_start: usize,
+ enclosing: usize,
+ first_frame_slot: u32,
+}
+
+#[repr(C)]
+pub struct SmooshFunctionScopeData {
+ bindings: CVec<COption<SmooshBindingName>>,
+ has_parameter_exprs: bool,
+ non_positional_formal_start: usize,
+ var_start: usize,
+ enclosing: usize,
+ first_frame_slot: u32,
+ function_index: usize,
+ is_arrow: bool,
+}
+
+#[repr(C, u8)]
+pub enum SmooshScopeData {
+ Global(SmooshGlobalScopeData),
+ Var(SmooshVarScopeData),
+ Lexical(SmooshLexicalScopeData),
+ Function(SmooshFunctionScopeData),
+}
+
+/// Convert single Scope data, resolving enclosing index with scope_index_map.
+fn convert_scope(scope: ScopeData, scope_index_map: &mut HashMap<usize, usize>) -> SmooshScopeData {
+ match scope {
+ ScopeData::Alias(_) => panic!("alias should be handled in convert_scopes"),
+ ScopeData::Global(data) => SmooshScopeData::Global(SmooshGlobalScopeData {
+ bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
+ let_start: data.let_start,
+ const_start: data.const_start,
+ }),
+ ScopeData::Var(data) => {
+ let enclosing: usize = data.enclosing.into();
+ SmooshScopeData::Var(SmooshVarScopeData {
+ bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
+ enclosing: *scope_index_map
+ .get(&enclosing)
+ .expect("Alias target should be earlier index"),
+ function_has_extensible_scope: data.function_has_extensible_scope,
+ first_frame_slot: data.first_frame_slot.into(),
+ })
+ }
+ ScopeData::Lexical(data) => {
+ let enclosing: usize = data.enclosing.into();
+ SmooshScopeData::Lexical(SmooshLexicalScopeData {
+ bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
+ const_start: data.const_start,
+ enclosing: *scope_index_map
+ .get(&enclosing)
+ .expect("Alias target should be earlier index"),
+ first_frame_slot: data.first_frame_slot.into(),
+ })
+ }
+ ScopeData::Function(data) => {
+ let enclosing: usize = data.enclosing.into();
+ SmooshScopeData::Function(SmooshFunctionScopeData {
+ bindings: CVec::from(
+ data.base
+ .bindings
+ .into_iter()
+ .map(|x| COption::from(x.map(|x| x.into())))
+ .collect(),
+ ),
+ has_parameter_exprs: data.has_parameter_exprs,
+ non_positional_formal_start: data.non_positional_formal_start,
+ var_start: data.var_start,
+ enclosing: *scope_index_map
+ .get(&enclosing)
+ .expect("Alias target should be earlier index"),
+ first_frame_slot: data.first_frame_slot.into(),
+ function_index: data.function_index.into(),
+ is_arrow: data.is_arrow,
+ })
+ }
+ }
+}
+
+/// Convert list of Scope data, removing aliases.
+/// Also create a map between original index into index into result vector
+/// without aliases.
+fn convert_scopes(
+ scopes: Vec<ScopeData>,
+ scope_index_map: &mut HashMap<usize, usize>,
+) -> CVec<SmooshScopeData> {
+ let mut result = Vec::with_capacity(scopes.len());
+ for (i, scope) in scopes.into_iter().enumerate() {
+ if let ScopeData::Alias(index) = scope {
+ let mapped_index = *scope_index_map
+ .get(&index.into())
+ .expect("Alias target should be earlier index");
+ scope_index_map.insert(i, mapped_index);
+
+ continue;
+ }
+
+ scope_index_map.insert(i, result.len());
+ result.push(convert_scope(scope, scope_index_map))
+ }
+
+ CVec::from(result)
+}
+
+#[repr(C)]
+pub struct SmooshScopeNote {
+ index: u32,
+ start: u32,
+ length: u32,
+ parent: u32,
+}
+
+impl From<ScopeNote> for SmooshScopeNote {
+ fn from(note: ScopeNote) -> Self {
+ let start = usize::from(note.start) as u32;
+ let end = usize::from(note.end) as u32;
+ let parent = match note.parent {
+ Some(index) => usize::from(index) as u32,
+ None => std::u32::MAX,
+ };
+ Self {
+ index: usize::from(note.index) as u32,
+ start,
+ length: end - start,
+ parent,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct SmooshRegExpItem {
+ pattern: usize,
+ global: bool,
+ ignore_case: bool,
+ multi_line: bool,
+ dot_all: bool,
+ sticky: bool,
+ unicode: bool,
+}
+
+impl From<RegExpItem> for SmooshRegExpItem {
+ fn from(data: RegExpItem) -> Self {
+ Self {
+ pattern: data.pattern.into(),
+ global: data.global,
+ ignore_case: data.ignore_case,
+ multi_line: data.multi_line,
+ dot_all: data.dot_all,
+ sticky: data.sticky,
+ unicode: data.unicode,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct SmooshImmutableScriptData {
+ pub main_offset: u32,
+ pub nfixed: u32,
+ pub nslots: u32,
+ pub body_scope_index: u32,
+ pub num_ic_entries: u32,
+ pub fun_length: u16,
+ pub bytecode: CVec<u8>,
+ pub scope_notes: CVec<SmooshScopeNote>,
+}
+
+#[repr(C)]
+pub struct SmooshSourceExtent {
+ pub source_start: u32,
+ pub source_end: u32,
+ pub to_string_start: u32,
+ pub to_string_end: u32,
+ pub lineno: u32,
+ pub column: u32,
+}
+
+#[repr(C, u8)]
+pub enum COption<T> {
+ Some(T),
+ None,
+}
+
+impl<T> COption<T> {
+ fn from(v: Option<T>) -> Self {
+ match v {
+ Option::Some(v) => COption::Some(v),
+ Option::None => COption::None,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct SmooshScriptStencil {
+ pub immutable_flags: u32,
+ pub gcthings: CVec<SmooshGCThing>,
+ pub immutable_script_data: COption<usize>,
+ pub extent: SmooshSourceExtent,
+ pub fun_name: COption<usize>,
+ pub fun_nargs: u16,
+ pub fun_flags: u16,
+ pub lazy_function_enclosing_scope_index: COption<usize>,
+ pub is_standalone_function: bool,
+ pub was_function_emitted: bool,
+ pub is_singleton_function: bool,
+}
+
+#[repr(C)]
+pub struct SmooshResult {
+ unimplemented: bool,
+ error: CVec<u8>,
+
+ scopes: CVec<SmooshScopeData>,
+ regexps: CVec<SmooshRegExpItem>,
+
+ scripts: CVec<SmooshScriptStencil>,
+ script_data_list: CVec<SmooshImmutableScriptData>,
+
+ all_atoms: *mut c_void,
+ all_atoms_len: usize,
+ slices: *mut c_void,
+ slices_len: usize,
+ allocator: *mut c_void,
+}
+
+impl SmooshResult {
+ fn unimplemented() -> Self {
+ Self::unimplemented_or_error(true, CVec::empty())
+ }
+
+ fn error(message: String) -> Self {
+ let error = CVec::from(format!("{}\0", message).into_bytes());
+ Self::unimplemented_or_error(false, error)
+ }
+
+ fn unimplemented_or_error(unimplemented: bool, error: CVec<u8>) -> Self {
+ Self {
+ unimplemented,
+ error,
+
+ scopes: CVec::empty(),
+ regexps: CVec::empty(),
+
+ scripts: CVec::empty(),
+ script_data_list: CVec::empty(),
+
+ all_atoms: std::ptr::null_mut(),
+ all_atoms_len: 0,
+ slices: std::ptr::null_mut(),
+ slices_len: 0,
+ allocator: std::ptr::null_mut(),
+ }
+ }
+}
+
+enum SmooshError {
+ GenericError(String),
+ NotImplemented,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_init() {
+ // Gecko might set a logger before we do, which is all fine; try to
+ // initialize ours, and reset the FilterLevel env_logger::try_init might
+ // have set to what it was in case of initialization failure
+ let filter = log::max_level();
+ match env_logger::try_init() {
+ Ok(_) => {}
+ Err(_) => {
+ log::set_max_level(filter);
+ }
+ }
+}
+
+fn convert_extent(extent: SourceExtent) -> SmooshSourceExtent {
+ SmooshSourceExtent {
+ source_start: extent.source_start,
+ source_end: extent.source_end,
+ to_string_start: extent.to_string_start,
+ to_string_end: extent.to_string_end,
+ lineno: extent.lineno,
+ column: extent.column,
+ }
+}
+
+fn convert_script(
+ script: ScriptStencil,
+ scope_index_map: &HashMap<usize, usize>,
+) -> SmooshScriptStencil {
+ let immutable_flags = script.immutable_flags.into();
+
+ let gcthings = CVec::from(
+ script
+ .gcthings
+ .into_iter()
+ .map(|x| convert_gcthing(x, scope_index_map))
+ .collect(),
+ );
+
+ let immutable_script_data = COption::from(script.immutable_script_data.map(|n| n.into()));
+
+ let extent = convert_extent(script.extent);
+
+ let fun_name = COption::from(script.fun_name.map(|n| n.into()));
+ let fun_nargs = script.fun_nargs;
+ let fun_flags = script.fun_flags.into();
+
+ let lazy_function_enclosing_scope_index =
+ COption::from(script.lazy_function_enclosing_scope_index.map(|index| {
+ *scope_index_map
+ .get(&index.into())
+ .expect("Alias target should be earlier index")
+ }));
+
+ let is_standalone_function = script.is_standalone_function;
+ let was_function_emitted = script.was_function_emitted;
+ let is_singleton_function = script.is_singleton_function;
+
+ SmooshScriptStencil {
+ immutable_flags,
+ gcthings,
+ immutable_script_data,
+ extent,
+ fun_name,
+ fun_nargs,
+ fun_flags,
+ lazy_function_enclosing_scope_index,
+ is_standalone_function,
+ was_function_emitted,
+ is_singleton_function,
+ }
+}
+
+fn convert_script_data(script_data: ImmutableScriptData) -> SmooshImmutableScriptData {
+ let main_offset = script_data.main_offset;
+ let nfixed = script_data.nfixed.into();
+ let nslots = script_data.nslots;
+ let body_scope_index = script_data.body_scope_index;
+ let num_ic_entries = script_data.num_ic_entries;
+ let fun_length = script_data.fun_length;
+
+ let bytecode = CVec::from(script_data.bytecode);
+
+ let scope_notes = CVec::from(
+ script_data
+ .scope_notes
+ .into_iter()
+ .map(|x| x.into())
+ .collect(),
+ );
+
+ SmooshImmutableScriptData {
+ main_offset,
+ nfixed,
+ nslots,
+ body_scope_index,
+ num_ic_entries,
+ fun_length,
+ bytecode,
+ scope_notes,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_run(
+ text: *const u8,
+ text_len: usize,
+ options: &SmooshCompileOptions,
+) -> SmooshResult {
+ let text = str::from_utf8(slice::from_raw_parts(text, text_len)).expect("Invalid UTF8");
+ let allocator = Box::new(bumpalo::Bump::new());
+ match smoosh(&allocator, text, options) {
+ Ok(result) => {
+ let mut scope_index_map = HashMap::new();
+
+ let scopes = convert_scopes(result.scopes, &mut scope_index_map);
+ let regexps = CVec::from(result.regexps.into_iter().map(|x| x.into()).collect());
+
+ let scripts = CVec::from(
+ result
+ .scripts
+ .into_iter()
+ .map(|x| convert_script(x, &scope_index_map))
+ .collect(),
+ );
+
+ let script_data_list = CVec::from(
+ result
+ .script_data_list
+ .into_iter()
+ .map(convert_script_data)
+ .collect(),
+ );
+
+ let all_atoms_len = result.atoms.len();
+ let all_atoms = Box::new(result.atoms);
+ let raw_all_atoms = Box::into_raw(all_atoms);
+ let opaque_all_atoms = raw_all_atoms as *mut c_void;
+
+ let slices_len = result.slices.len();
+ let slices = Box::new(result.slices);
+ let raw_slices = Box::into_raw(slices);
+ let opaque_slices = raw_slices as *mut c_void;
+
+ let raw_allocator = Box::into_raw(allocator);
+ let opaque_allocator = raw_allocator as *mut c_void;
+
+ SmooshResult {
+ unimplemented: false,
+ error: CVec::empty(),
+
+ scopes,
+ regexps,
+
+ scripts,
+ script_data_list,
+
+ all_atoms: opaque_all_atoms,
+ all_atoms_len,
+ slices: opaque_slices,
+ slices_len,
+ allocator: opaque_allocator,
+ }
+ }
+ Err(SmooshError::GenericError(message)) => SmooshResult::error(message),
+ Err(SmooshError::NotImplemented) => SmooshResult::unimplemented(),
+ }
+}
+
+#[repr(C)]
+pub struct SmooshParseResult {
+ unimplemented: bool,
+ error: CVec<u8>,
+}
+
+fn convert_parse_result<'alloc, T>(r: jsparagus::parser::Result<T>) -> SmooshParseResult {
+ match r {
+ Ok(_) => SmooshParseResult {
+ unimplemented: false,
+ error: CVec::empty(),
+ },
+ Err(err) => match *err {
+ ParseError::NotImplemented(_) => {
+ let message = err.message();
+ SmooshParseResult {
+ unimplemented: true,
+ error: CVec::from(format!("{}\0", message).into_bytes()),
+ }
+ }
+ _ => {
+ let message = err.message();
+ SmooshParseResult {
+ unimplemented: false,
+ error: CVec::from(format!("{}\0", message).into_bytes()),
+ }
+ }
+ },
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_test_parse_script(
+ text: *const u8,
+ text_len: usize,
+) -> SmooshParseResult {
+ let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) {
+ Ok(text) => text,
+ Err(_) => {
+ return SmooshParseResult {
+ unimplemented: false,
+ error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()),
+ };
+ }
+ };
+ let allocator = bumpalo::Bump::new();
+ let parse_options = ParseOptions::new();
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ convert_parse_result(parse_script(
+ &allocator,
+ text,
+ &parse_options,
+ atoms,
+ slices,
+ ))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_test_parse_module(
+ text: *const u8,
+ text_len: usize,
+) -> SmooshParseResult {
+ let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) {
+ Ok(text) => text,
+ Err(_) => {
+ return SmooshParseResult {
+ unimplemented: false,
+ error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()),
+ };
+ }
+ };
+ let allocator = bumpalo::Bump::new();
+ let parse_options = ParseOptions::new();
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ convert_parse_result(parse_module(
+ &allocator,
+ text,
+ &parse_options,
+ atoms,
+ slices,
+ ))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_free_parse_result(result: SmooshParseResult) {
+ let _ = result.error.into();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_get_atom_at(result: SmooshResult, index: usize) -> *const c_char {
+ let all_atoms = result.all_atoms as *const Vec<&str>;
+ let atom = (*all_atoms)[index];
+ atom.as_ptr() as *const c_char
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_get_atom_len_at(result: SmooshResult, index: usize) -> usize {
+ let all_atoms = result.all_atoms as *const Vec<&str>;
+ let atom = (*all_atoms)[index];
+ atom.len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_get_slice_at(result: SmooshResult, index: usize) -> *const c_char {
+ let slices = result.slices as *const Vec<&str>;
+ let slice = (*slices)[index];
+ slice.as_ptr() as *const c_char
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_get_slice_len_at(result: SmooshResult, index: usize) -> usize {
+ let slices = result.slices as *const Vec<&str>;
+ let slice = (*slices)[index];
+ slice.len()
+}
+
+unsafe fn free_script(script: SmooshScriptStencil) {
+ let _ = script.gcthings.into();
+}
+
+unsafe fn free_script_data(script_data: SmooshImmutableScriptData) {
+ let _ = script_data.bytecode.into();
+ let _ = script_data.scope_notes.into();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn smoosh_free(result: SmooshResult) {
+ let _ = result.error.into();
+
+ let _ = result.scopes.into();
+ let _ = result.regexps.into();
+
+ for fun in result.scripts.into() {
+ free_script(fun);
+ }
+
+ for script_data in result.script_data_list.into() {
+ free_script_data(script_data);
+ }
+
+ if !result.all_atoms.is_null() {
+ let _ = Box::from_raw(result.all_atoms as *mut Vec<&str>);
+ }
+ if !result.slices.is_null() {
+ let _ = Box::from_raw(result.slices as *mut Vec<&str>);
+ }
+ if !result.allocator.is_null() {
+ let _ = Box::from_raw(result.allocator as *mut bumpalo::Bump);
+ }
+}
+
+fn smoosh<'alloc>(
+ allocator: &'alloc bumpalo::Bump,
+ text: &'alloc str,
+ options: &SmooshCompileOptions,
+) -> Result<EmitResult<'alloc>, SmooshError> {
+ let parse_options = ParseOptions::new();
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ let text_length = text.len();
+
+ let parse_result = match parse_script(
+ &allocator,
+ text,
+ &parse_options,
+ atoms.clone(),
+ slices.clone(),
+ ) {
+ Ok(result) => result,
+ Err(err) => match *err {
+ ParseError::NotImplemented(_) => {
+ println!("Unimplemented: {}", err.message());
+ return Err(SmooshError::NotImplemented);
+ }
+ _ => {
+ return Err(SmooshError::GenericError(err.message()));
+ }
+ },
+ };
+
+ let extent = SourceExtent::top_level_script(text_length.try_into().unwrap(), 1, 0);
+ let mut emit_options = EmitOptions::new(extent);
+ emit_options.no_script_rval = options.no_script_rval;
+ let script = parse_result.unbox();
+ match emit(
+ allocator.alloc(Program::Script(script)),
+ &emit_options,
+ atoms.replace(SourceAtomSet::new_uninitialized()),
+ slices.replace(SourceSliceList::new()),
+ ) {
+ Ok(result) => Ok(result),
+ Err(EmitError::NotImplemented(message)) => {
+ println!("Unimplemented: {}", message);
+ return Err(SmooshError::NotImplemented);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn it_works() {
+ assert_eq!(2 + 2, 4);
+ }
+}