Skip to content

Commit

Permalink
example && guidelines to contributing.md
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelb committed Nov 5, 2020
1 parent 3e947ee commit c7188c5
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 4 deletions.
14 changes: 12 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ What do I write, and where?

Yeah cool but what _code_ goes inside?

-> Inside, you must define a struct that implement the **Interpreter** trait. Have a look at existing implementations to get the idea. Make sure to respect the [conventions](#conventions)
-> Inside, you must define a struct that implement the **Interpreter** trait. Have a look at existing implementations to get the idea. Make sure to respect the [conventions](#conventions). The "example.rs" interpreter is a good starting point.

I just compiled, how do I test my code quickly?

Expand Down Expand Up @@ -92,11 +92,21 @@ A program (struct with methods) that can fetch code, execute it and return the r
- Names for interpreters should be unique. Include filenames, and also the name returned by `get_name()` that should be identical (case difference is tolerated).
- Extra files for the same interpreter go into a subdfolder alongside the interpreter's main file. The subfolder has the same name as the file, minus the extension.
- The interpreter try to follow (and create by itself) SupportLevel hints when possible; for example, will not try to parse an entire project into when it has been determined SupportLevel::Line is enough to run the submitted code.
- The interpreter should not panic (unless fatal), but return the SniprunError as suggested by the Interpreter trait
- The interpreter should try not to panic, it'll be nicer if the various errors can be converted and returned as SniprunError as defined in src/interpreter.rs and suggested by the Interpreter trait

## Contribute to Sniprun itself

Well you are welcome to submit a PR, as long as you mind those points:

- Your changes do not break any interpreter, even partially.
- If needed (eg for when your changes touches a core part of Sniprun such as the DataHolder), you have tested your changes with every interpreter.

## Sniprun Mindset

To pay attention to, when writing an interpreter or changes:

- **Minimum code retrieval** : Sniprun should only fetch from the buffer/file the bare minimum necessary to get working.
- **Allow snips from incomplete files** : if you need to read a bigger part of the file than the data provided by sniprun (in DataHolder), you should NOT fail because the file miss a '}' 35 lines after the code snip.
- **IO optimization** : it's OK if you write 3 files each time sniprun fires. It's not OK if you re-index a whole project and write a 50Mo file. Overall this is a pretty relaxed rule, as most code sent to sniprun (to then write etc...) is very short, a few lines at most.
- **Code clarity** : at least comments for non-trivial parts, 'good code' is valued even if I get, and did that myself, than sometimes dirty hacks are necessary.
- **Documentation** : not extensively required, but limitations and subtilities, if any, of your interpreter should be written a the doc/interpreter_name.md file.
4 changes: 2 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn main() -> Result<(), std::io::Error> {

for path in fs::read_dir(out_dir).unwrap() {
let plugin = path.unwrap().file_name().into_string().unwrap();
if plugin == "mod.rs" {
if plugin == "mod.rs" || plugin == "example.rs" {
continue;
}
if !plugin.ends_with(".rs") {
Expand All @@ -46,7 +46,7 @@ fn main() -> Result<(), std::io::Error> {

for path in fs::read_dir(out_dir).unwrap() {
let mut plugin = path.unwrap().file_name().into_string().unwrap();
if plugin == "mod.rs" || plugin == "import.rs" {
if plugin == "mod.rs" || plugin == "import.rs" || plugin == "example.rs" {
continue;
}
if !plugin.ends_with(".rs") {
Expand Down
155 changes: 155 additions & 0 deletions src/interpreters/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//Interpreter:| Name | language |
//############|_____________________|_____________|________________<- delimiters to help formatting,
//###########| Interpretername | language | comment
// Keep (but modify the first line after the :) if you wish to have this interpreter listed via
// SnipList

// Be sure to read the CONTRIBUTING.md file :-)

#[derive(Clone)]
#[allow(non_camel_case_types)]
// For example, Rust_original is a good name for a first rust interpreter
pub struct Language_subname {
support_level: SupportLevel,
data: DataHolder,
code: String,

///specific to compiled languages
language_work_dir: String,
bin_path: String,
main_file_path: String,
// you can and should add field as needed
}

//necessary boilerplate, you don't need to implement that if you want a Bloc support level
//interpreter (the easiest && most common)
impl ReplLikeInterpreter for Language_subname {}

impl Interpreter for Rust_original {
fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box<Rust_original> {
//create a subfolder in the cache folder
let lwd = data.work_dir.clone() + "/language_subname";
let mut builder = DirBuilder::new();
builder.recursive(true);
builder
.create(&lwd)
.expect("Could not create directory for rust-original");

//pre-create string pointing to main file's and binary's path
let mfp = lwd.clone() + "/main.extension";
let bp = lwd.clone() + "/main"; // remove extension so binary is named 'main'
Box::new(Rust_original {
data,
support_level,
code: String::new(),
rust_work_dir: lwd,
bin_path: bp,
main_file_path: mfp,
})
}

fn get_supported_languages() -> Vec<String> {
vec![
//':set ft?' in nvim to get the filetype of opened file
String::from("language_filetype"),
String::from("extension"), //should not be necessary, but just in case
// another similar name (like python and python3)?
]
}

fn get_name() -> String {
// get your interpreter name
String::from("Language_subname")
}

fn get_current_level(&self) -> SupportLevel {
self.support_level
}
fn set_current_level(&mut self, level: SupportLevel) {
self.support_level = level;
}

fn get_data(&self) -> DataHolder {
self.data.clone()
}

fn get_max_support_level() -> SupportLevel {
//define the max level support of the interpreter (see readme for definitions)
SupportLevel::Bloc
}

fn fetch_code(&mut self) -> Result<(), SniprunError> {
//here if you detect conditions that make higher support level impossible,
//or unecessary, you should set the current level down. Then you will be able to
//ignore maybe-heavy code that won't be needed anyway

//add code from data to self.code
if !self
.data
.current_bloc
.replace(&[' ', '\t', '\n', '\r'][..], "")
.is_empty()
&& self.support_level >= SupportLevel::Bloc
{
// if bloc is not pseudo empty and has Bloc current support level
self.code = self.data.current_bloc.clone();
} else if !self.data.current_line.replace(" ", "").is_empty()
&& self.support_level >= SupportLevel::Line
{
self.code = self.data.current_line.clone();
} else {
self.code = String::from("");
}

// now self.code contains the line or bloc of code wanted :-)
Ok(())
}

fn add_boilerplate(&mut self) -> Result<(), SniprunError> {
// an example following Rust's syntax
self.code = String::from("fn main() {") + &self.code + "}";
Ok(())
}

fn build(&mut self) -> Result<(), SniprunError> {
//write code to file
let mut _file =
File::create(&self.main_file_path).expect("Failed to create file for rust-original");
// IO errors can be ignored, or handled into a proper SniprunError
// If you panic, it should not be too dangerous for anyone
write(&self.main_file_path, &self.code).expect("Unable to write to file for rust-original");

//compile it (to the bin_path that arleady points to the rigth path)
let output = Command::new("compiler")
.arg("--optimize") // for short snippets, that may contain a long loop
.arg("--out-dir")
.arg(&self.language_work_dir)
.arg(&self.main_file_path)
.output()
.expect("Unable to start process");

//TODO if relevant, return the error number (parse it from stderr)
if !output.status.success() {
return Err(SniprunError::CompilationError("".to_string()));
} else {
return Ok(());
}
}

fn execute(&mut self) -> Result<String, SniprunError> {
//run th binary and get the std output (or stderr)
let output = Command::new(&self.bin_path)
.output()
.expect("Unable to start process");

if output.status.success() {
//return stdout
return Ok(String::from_utf8(output.stdout).unwrap());
} else {
// return stderr
return Err(SniprunError::RuntimeError(
String::from_utf8(output.stderr).unwrap(),
));
}
}
}

0 comments on commit c7188c5

Please sign in to comment.