├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── input.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rie" 3 | version = "0.1.0" 4 | authors = ["Fedor Logachev ", "gendx"] 5 | license = "MIT" 6 | keywords = ["repl"] 7 | description = "REPL like interactive code editor" 8 | repository = "https://github.com/not-fl3/rie" 9 | 10 | [dependencies] 11 | tempdir = "0.3" 12 | rustyline = "1.0.0" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fedor Logachev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rie 2 | 3 | RIE is REPL-like interactive code editor. 4 | ``` 5 | # cargo install rie 6 | # rie 7 | 8 | >> :2 * 2 9 | = 4 10 | 11 | >> let x = 2 12 | = 13 | >> :x * 2 14 | = 4 15 | 16 | >> let f = |x : i32| x * x 17 | = 18 | >> :f(4) 19 | = 16 20 | 21 | >> % 22 | 0 fn main() { 23 | 1 let x = 2; 24 | 2 let f = |x : i32| x * x; 25 | 3 } 26 | >> %d 2 27 | 0 fn main() { 28 | 1 let x = 2; 29 | 2 } 30 | >> {{ 31 | >>> fn foo() { 32 | >>> println!("Hello world"); 33 | >>> } 34 | >>> }} 35 | = 36 | >> :foo() 37 | = Hello world 38 | () 39 | ``` 40 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use rustyline::Editor; 2 | use rustyline::error::ReadlineError; 3 | 4 | #[derive(Clone)] 5 | pub enum ReplCommand { 6 | PrintValue(String), 7 | AddExpression(String), 8 | PrintCode, 9 | Exit, 10 | Nothing, 11 | RemoveLines(u32), 12 | } 13 | 14 | pub struct Input { 15 | editor: Editor<()>, 16 | input_buffer: Option, 17 | greeting: String, 18 | } 19 | 20 | impl Input { 21 | pub fn new() -> Input { 22 | Input { 23 | editor: Editor::<()>::new(), 24 | input_buffer: None, 25 | greeting: ">> ".to_string(), 26 | } 27 | } 28 | 29 | pub fn read(&mut self) -> ReplCommand { 30 | let readline = self.editor.readline(&self.greeting); 31 | 32 | match readline { 33 | Ok(line) => if let Some(buffer) = self.input_buffer.clone() { 34 | if line.chars().nth(0).map_or(false, |c| c == '}') 35 | && line.chars().nth(1).map_or(false, |c| c == '}') 36 | { 37 | self.greeting = ">> ".to_string(); 38 | self.editor.add_history_entry(&buffer); 39 | let cmd = ReplCommand::AddExpression(self.input_buffer.take().unwrap()); 40 | return cmd; 41 | } else { 42 | self.input_buffer.as_mut().unwrap().push_str(&line); 43 | return ReplCommand::Nothing; 44 | } 45 | } else { 46 | match ( 47 | line.chars().nth(0).map_or(None, Some), 48 | line.chars().nth(1).map_or(None, Some), 49 | ) { 50 | (Some('%'), Some('d')) => { 51 | let mut splitted = line.split(' '); 52 | if let Some(number) = splitted 53 | .nth(1) 54 | .and_then(|number| number.parse::().ok()) 55 | { 56 | return ReplCommand::RemoveLines(number); 57 | } 58 | return ReplCommand::Nothing; 59 | } 60 | 61 | (Some('%'), _) => { 62 | self.editor.add_history_entry(&line); 63 | return ReplCommand::PrintCode; 64 | } 65 | (Some(':'), Some(_)) => { 66 | self.editor.add_history_entry(&line); 67 | return ReplCommand::PrintValue(line[1..].to_string()); 68 | } 69 | (Some('{'), Some('{')) => { 70 | self.greeting = ">>> ".to_string(); 71 | self.input_buffer = Some(String::new()); 72 | return ReplCommand::Nothing; 73 | } 74 | (None, _) => return ReplCommand::Nothing, 75 | _ => { 76 | self.editor.add_history_entry(&line); 77 | return ReplCommand::AddExpression(line.to_string()); 78 | } 79 | } 80 | }, 81 | Err(ReadlineError::Interrupted) | 82 | Err(ReadlineError::Eof) | 83 | Err(_) => { 84 | ReplCommand::Exit 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rustyline; 2 | extern crate tempdir; 3 | 4 | mod input; 5 | 6 | use std::path::PathBuf; 7 | use std::process::Command; 8 | 9 | use tempdir::TempDir; 10 | 11 | use input::{Input, ReplCommand}; 12 | 13 | type ExecutionResult = String; 14 | type CompilationError = String; 15 | type RuntimeError = String; 16 | 17 | struct CompiledFile { 18 | _temp_dir: TempDir, 19 | binary_path: PathBuf, 20 | } 21 | 22 | struct InternalFunction { 23 | body: Vec, 24 | } 25 | 26 | impl InternalFunction { 27 | fn new() -> InternalFunction { 28 | InternalFunction { body: vec![] } 29 | } 30 | 31 | fn file_contents(&self) -> String { 32 | format!( 33 | "fn main() {{ {}}}", 34 | self.body 35 | .iter() 36 | .fold(String::new(), |acc, str| { acc + str }) 37 | ) 38 | } 39 | 40 | fn try_compile(&self) -> Result { 41 | use std::io::Write; 42 | use std::fs::File; 43 | 44 | let source_filename = "tmp.rs"; 45 | let binary_filename = "tmp_binary"; 46 | let dir = TempDir::new("rustci").unwrap(); 47 | let file_path = dir.path().join(source_filename); 48 | let out_file_path = dir.path().join(binary_filename); 49 | let mut file = File::create(&file_path).unwrap(); 50 | 51 | write!(&mut file, "{}", self.file_contents()).unwrap(); 52 | 53 | let output = Command::new("rustc") 54 | .arg(&file_path) 55 | .arg("-o") 56 | .arg(&out_file_path) 57 | .output() 58 | .unwrap(); 59 | 60 | if output.status.success() { 61 | Ok(CompiledFile { 62 | _temp_dir: dir, 63 | binary_path: out_file_path, 64 | }) 65 | } else { 66 | let stdout = String::from_utf8(output.stdout).unwrap(); 67 | let stderr = String::from_utf8(output.stderr).unwrap(); 68 | 69 | Err(format!( 70 | "stdout: {}\nstderr: {}\nerrorcode: {:?}", 71 | stdout, 72 | stderr, 73 | output.status 74 | )) 75 | } 76 | } 77 | 78 | fn try_execute( 79 | &self, 80 | compiled_file: CompiledFile, 81 | ) -> Result { 82 | let output = Command::new(compiled_file.binary_path).output().unwrap(); 83 | 84 | if output.status.success() { 85 | Ok(String::from_utf8(output.stdout).unwrap()) 86 | } else { 87 | let stdout = String::from_utf8(output.stdout).unwrap(); 88 | let stderr = String::from_utf8(output.stderr).unwrap(); 89 | 90 | Err(format!( 91 | "stdout: {}\nstderr: {}\nerrorcode: {:?}", 92 | stdout, 93 | stderr, 94 | output.status 95 | )) 96 | } 97 | } 98 | 99 | fn append_line(&self, line: String) -> InternalFunction { 100 | let mut body = self.body.clone(); 101 | body.push(line); 102 | InternalFunction { body: body } 103 | } 104 | } 105 | 106 | struct Repl { 107 | function: InternalFunction, 108 | } 109 | 110 | impl Repl { 111 | pub fn process_command(&mut self, command: ReplCommand) -> bool { 112 | match command { 113 | ReplCommand::PrintCode => { 114 | let begin = "fn main() {\n".to_owned(); 115 | let end = "}".to_owned(); 116 | let lines = ::std::iter::once(begin) 117 | .chain(self.function.body.iter().map(|s| " ".to_owned() + s)) 118 | .chain(::std::iter::once(end)); 119 | 120 | println!( 121 | "{}", 122 | lines.enumerate().fold( 123 | String::new(), 124 | |acc, (i, str)| acc + &format!("{} {}", i, str) 125 | ) 126 | ); 127 | true 128 | } 129 | ReplCommand::RemoveLines(line) => { 130 | if line >= 1 { 131 | self.function.body.truncate((line - 1) as usize); 132 | } 133 | self.process_command(ReplCommand::PrintCode) 134 | } 135 | ReplCommand::Nothing => true, 136 | ReplCommand::Exit => false, 137 | ReplCommand::AddExpression(line) => { 138 | let newfunc = self.function.append_line(format!("{};\n", line)); 139 | 140 | match newfunc 141 | .try_compile() 142 | .and_then(|file| newfunc.try_execute(file)) 143 | { 144 | Ok(result) => { 145 | println!("= {}", result); 146 | self.function = newfunc 147 | } 148 | Err(error) => { 149 | println!("ERR {}", error); 150 | } 151 | } 152 | true 153 | } 154 | ReplCommand::PrintValue(line) => { 155 | let newfunc = self.function 156 | .append_line(format!("println!(\"{{:?}}\", {{ {} }});", line)); 157 | 158 | let _ = newfunc 159 | .try_compile() 160 | .and_then(|file| newfunc.try_execute(file)) 161 | .map(|result| println!("= {}", result)) 162 | .map_err(|err| println!("ERR {}", err)); 163 | true 164 | } 165 | } 166 | } 167 | } 168 | 169 | fn main() { 170 | let mut repl = Repl { 171 | function: InternalFunction::new(), 172 | }; 173 | let mut input = Input::new(); 174 | 175 | while !repl.process_command(input.read()) { 176 | // not quit yet 177 | } 178 | } 179 | --------------------------------------------------------------------------------