├── .gitignore ├── LICENSE ├── README.md ├── RustPython ├── Cargo.toml └── src │ ├── builtins.rs │ └── main.rs ├── compile_code.py ├── docs ├── builtins.md ├── compat-test.md ├── frameTODO.txt ├── notes.md ├── reference.md ├── sharing.md ├── slides │ ├── 201708_COSCUP │ │ ├── pic │ │ │ ├── car_cutaway.jpg │ │ │ ├── electronic_parts.jpg │ │ │ ├── ice-cream.jpg │ │ │ ├── python-logo.png │ │ │ ├── repo_QR.png │ │ │ └── rust-servo.png │ │ └── slide.md │ └── intro │ │ ├── pic │ │ └── ice-cream.jpg │ │ └── slide.md └── study.md ├── init_env.sh ├── python_compiler ├── Cargo.toml ├── examples │ └── compile.rs └── src │ ├── lib.rs │ └── python_compiler.rs ├── requirements.txt ├── test.py ├── test.sh ├── test_all.bat ├── test_all.sh └── tests ├── 3.1.2.13.py ├── 3.1.2.16.py ├── 3.1.2.18.py ├── 3.1.2.19.py ├── 3.1.3.2.py ├── 3.1.3.4.py ├── basic_types.py ├── builtin_len.py ├── cast.py ├── floats.py ├── for.py ├── function.py ├── function_args.py ├── function_nested.py ├── if.py ├── intro ├── 3.1.1.1.py ├── 3.1.1.2.py ├── 3.1.1.3.py ├── 3.1.1.4.py ├── 3.1.1.6.py ├── 3.1.2.1.py ├── 3.1.2.10.py ├── 3.1.2.3.py ├── 3.1.2.4.py ├── 3.1.2.5.py └── 3.1.2.6.py ├── list.py ├── loop.py ├── math.py ├── minimum.py ├── print_const.py ├── tuple.py ├── types.py ├── variables.py ├── xfail_3.1.2.17.py └── xfail_assert.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | tests/*.bytecode 3 | RustPython/Cargo.lock 4 | RustPython/target 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shing Lyu 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 | RustPython 2 | ============== 3 | # This repository is now part of the [RustPython/RustPython](https://github.com/RustPython/RustPython) project. Please go to the [RustPython/RustPython](https://github.com/RustPython/RustPython) project to star or contribute! 4 | 5 | ------------------------------ 6 | 7 | A Python interpreter written in Rust 8 | 9 | # Installation 10 | 11 | ``` 12 | bash init_env.sh 13 | ``` 14 | 15 | # Run 16 | 17 | ``` 18 | ./test.sh # compile and run 19 | ./test.sh --bytecode # print the bytecode in JSON 20 | ./test.sh --dis # Run python -m dis 21 | ``` 22 | 23 | ## Manual 24 | Given a python file `test.py` 25 | 26 | ``` 27 | python compile_code.py test.py > test.bytecode 28 | 29 | cd RustPython 30 | cargo run ../test.bytecode 31 | ``` 32 | 33 | # Testing & debugging 34 | 35 | ``` 36 | ./test_all.sh # Run all tests under tests/ 37 | ``` 38 | 39 | * If a test is expected to fail or raise exception, add `xfail_*` prefix to the filename. 40 | 41 | ## Logging 42 | 43 | ``` 44 | RUST_LOG=debug ./tests_all.sh 45 | ``` 46 | 47 | # TODOs 48 | * Native types => Partial 49 | * Control flow => if(v) 50 | * assert => OK 51 | * Structural types (list, tuple, object) 52 | * Strings 53 | * Function calls => Blocked by bytecode serializer 54 | * Modules import 55 | * Generators 56 | 57 | 58 | # Goals 59 | * Support all builtin functions 60 | * Runs the [pybenchmark](https://pybenchmarks.org/) benchmark test 61 | * Run famous/popular python modules (which?) 62 | 63 | * Compatible with CPython 3.6 64 | 65 | # Rust version 66 | rustc 1.20.0-nightly 67 | 68 | -------------------------------------------------------------------------------- /RustPython/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustpython" 3 | version = "0.1.0" 4 | authors = ["Shing Lyu "] 5 | 6 | [dependencies] 7 | log = "0.3" 8 | env_logger = "0.3" 9 | serde = "0.8.22" 10 | serde_derive = "0.8" 11 | serde_json = "0.8" 12 | -------------------------------------------------------------------------------- /RustPython/src/builtins.rs: -------------------------------------------------------------------------------- 1 | use NativeType; 2 | use std::rc::Rc; 3 | use std::ops::Deref; 4 | 5 | pub fn print(args: Vec>) -> NativeType { 6 | for elem in args { 7 | // TODO: figure out how python's print vectors 8 | match elem.deref() { 9 | &NativeType::NoneType => println!("None"), 10 | &NativeType::Boolean(ref b)=> { 11 | if *b { 12 | println!("True"); 13 | } else { 14 | println!("False"); 15 | } 16 | }, 17 | &NativeType::Int(ref x) => println!("{}", x), 18 | &NativeType::Float(ref x) => println!("{}", x), 19 | &NativeType::Str(ref x) => println!("{}", x), 20 | &NativeType::Unicode(ref x) => println!("{}", x), 21 | _ => panic!("Print for {:?} not implemented yet", elem), 22 | /* 23 | List(Vec), 24 | Tuple(Vec), 25 | Iter(Vec), // TODO: use Iterator instead 26 | Code(PyCodeObject), 27 | Function(Function), 28 | #[serde(skip_serializing, skip_deserializing)] 29 | NativeFunction(fn(Vec) -> NativeType ), 30 | */ 31 | } 32 | } 33 | NativeType::NoneType 34 | } 35 | 36 | pub fn len(args: Vec>) -> NativeType { 37 | if args.len() != 1 { 38 | panic!("len(s) expects exactly one parameter"); 39 | } 40 | let len = match args[0].deref() { 41 | &NativeType::List(ref l) => l.borrow().len(), 42 | &NativeType::Tuple(ref t) => t.len(), 43 | &NativeType::Str(ref s) => s.len(), 44 | _ => panic!("TypeError: object of this type has no len()") 45 | }; 46 | NativeType::Int(len as i32) 47 | } 48 | -------------------------------------------------------------------------------- /RustPython/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate env_logger; 4 | 5 | #[macro_use] 6 | extern crate serde_derive; 7 | extern crate serde_json; 8 | 9 | //extern crate eval; use eval::eval::*; 10 | use std::collections::HashMap; 11 | use std::cell::RefCell; 12 | use std::env; 13 | use std::fs::File; 14 | use std::io::prelude::*; 15 | use std::rc::Rc; 16 | use std::ops::Deref; 17 | 18 | mod builtins; 19 | 20 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 21 | pub enum NativeType{ 22 | NoneType, 23 | Boolean(bool), 24 | Int(i32), 25 | Float(f64), 26 | Str(String), 27 | Unicode(String), 28 | #[serde(skip_serializing, skip_deserializing)] 29 | List(RefCell>), 30 | Tuple(Vec), 31 | Iter(Vec), // TODO: use Iterator instead 32 | Code(PyCodeObject), 33 | Function(Function), 34 | Slice(Option, Option, Option), // start, stop, step 35 | #[serde(skip_serializing, skip_deserializing)] 36 | NativeFunction(fn(Vec>) -> NativeType ), 37 | } 38 | 39 | const CMP_OP: &'static [&'static str] = &[">", 40 | "<=", 41 | "==", 42 | "!=", 43 | ">", 44 | ">=", 45 | "in", 46 | "not in", 47 | "is", 48 | "is not", 49 | "exception match", 50 | "BAD" 51 | ]; 52 | 53 | #[derive(Clone)] 54 | struct Block { 55 | block_type: String, //Enum? 56 | handler: usize // The destination we should jump to if the block finishes 57 | // level? 58 | } 59 | 60 | struct Frame { 61 | // TODO: We are using Option in stack for handline None return value 62 | code: PyCodeObject, 63 | // We need 1 stack per frame 64 | stack: Vec>, // The main data frame of the stack machine 65 | blocks: Vec, // Block frames, for controling loops and exceptions 66 | globals: HashMap>, // Variables 67 | locals: HashMap>, // Variables 68 | labels: HashMap, // Maps label id to line number, just for speedup 69 | lasti: usize, // index of last instruction ran 70 | return_value: NativeType, 71 | why: String, //Not sure why we need this //Maybe use a enum if we have fininte options 72 | // cmp_op: Vec<&'a Fn(NativeType, NativeType) -> bool>, // TODO: change compare to a function list 73 | } 74 | 75 | impl Frame { 76 | /// Get the current bytecode offset calculated from curr_frame.lasti 77 | fn get_bytecode_offset(&self) -> Option { 78 | // Linear search the labels HashMap, inefficient. Consider build a reverse HashMap 79 | let mut last_offset = None; 80 | for (offset, instr_idx) in self.labels.iter() { 81 | if *instr_idx == self.lasti { 82 | last_offset = Some(*offset) 83 | } 84 | } 85 | last_offset 86 | } 87 | } 88 | 89 | #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] 90 | pub struct Function { 91 | code: PyCodeObject 92 | } 93 | 94 | impl Function { 95 | fn new(code: PyCodeObject) -> Function { 96 | Function { 97 | code: code 98 | } 99 | } 100 | } 101 | 102 | 103 | struct VirtualMachine{ 104 | frames: Vec, 105 | } 106 | 107 | impl VirtualMachine { 108 | fn new() -> VirtualMachine { 109 | VirtualMachine { 110 | frames: vec![], 111 | } 112 | } 113 | 114 | fn curr_frame(&mut self) -> &mut Frame { 115 | self.frames.last_mut().unwrap() 116 | } 117 | 118 | fn pop_frame(&mut self) { 119 | self.frames.pop().unwrap(); 120 | } 121 | 122 | fn unwind(&mut self, reason: String) { 123 | let curr_frame = self.curr_frame(); 124 | let curr_block = curr_frame.blocks[curr_frame.blocks.len()-1].clone(); // use last? 125 | curr_frame.why = reason; // Why do we need this? 126 | debug!("block status: {:?}, {:?}", curr_block.block_type, curr_frame.why); 127 | match (curr_block.block_type.as_ref(), curr_frame.why.as_ref()) { 128 | ("loop", "break") => { 129 | curr_frame.lasti = curr_block.handler; //curr_frame.labels[curr_block.handler]; // Jump to the end 130 | // Return the why as None 131 | curr_frame.blocks.pop(); 132 | }, 133 | ("loop", "none") => (), //skipped 134 | _ => panic!("block stack operation not implemented") 135 | } 136 | } 137 | 138 | // Can we get rid of the code paramter? 139 | 140 | fn make_frame(&self, code: PyCodeObject, callargs: HashMap>, globals: Option>>) -> Frame { 141 | //populate the globals and locals 142 | let mut labels = HashMap::new(); 143 | let mut curr_offset = 0; 144 | for (idx, op) in code.co_code.iter().enumerate() { 145 | labels.insert(curr_offset, idx); 146 | curr_offset += op.0; 147 | } 148 | //TODO: This is wrong, check https://github.com/nedbat/byterun/blob/31e6c4a8212c35b5157919abff43a7daa0f377c6/byterun/pyvm2.py#L95 149 | let globals = match globals { 150 | Some(g) => g, 151 | None => HashMap::new(), 152 | }; 153 | let mut locals = globals; 154 | locals.extend(callargs); 155 | 156 | //TODO: move this into the __builtin__ module when we have a module type 157 | locals.insert("print".to_string(), Rc::new(NativeType::NativeFunction(builtins::print))); 158 | locals.insert("len".to_string(), Rc::new(NativeType::NativeFunction(builtins::len))); 159 | Frame { 160 | code: code, 161 | stack: vec![], 162 | blocks: vec![], 163 | // save the callargs as locals 164 | globals: locals.clone(), 165 | locals: locals, 166 | labels: labels, 167 | lasti: 0, 168 | return_value: NativeType::NoneType, 169 | why: "none".to_string(), 170 | } 171 | } 172 | 173 | // The Option is the return value of the frame, remove when we have implemented frame 174 | // TODO: read the op codes directly from the internal code object 175 | fn run_frame(&mut self, frame: Frame) -> NativeType { 176 | self.frames.push(frame); 177 | 178 | //let mut why = None; 179 | // Change this to a loop for jump 180 | loop { 181 | //while curr_frame.lasti < curr_frame.code.co_code.len() { 182 | let op_code = { 183 | let curr_frame = self.curr_frame(); 184 | let op_code = curr_frame.code.co_code[curr_frame.lasti].clone(); 185 | curr_frame.lasti += 1; 186 | op_code 187 | }; 188 | let why = self.dispatch(op_code); 189 | /*if curr_frame.blocks.len() > 0 { 190 | self.manage_block_stack(&why); 191 | } 192 | */ 193 | if let Some(_) = why { 194 | break; 195 | } 196 | } 197 | let return_value = { 198 | //let curr_frame = self.frames.last_mut().unwrap(); 199 | self.curr_frame().return_value.clone() 200 | }; 201 | self.pop_frame(); 202 | return_value 203 | } 204 | 205 | fn run_code(&mut self, code: PyCodeObject) { 206 | let frame = self.make_frame(code, HashMap::new(), None); 207 | self.run_frame(frame); 208 | // check if there are any leftover frame, fail if any 209 | } 210 | 211 | fn dispatch(&mut self, op_code: (usize, String, Option)) -> Option { 212 | { 213 | debug!("stack:{:?}", self.curr_frame().stack); 214 | debug!("env :{:?}", self.curr_frame().locals); 215 | debug!("Executing op code: {:?}", op_code); 216 | } 217 | match (op_code.1.as_ref(), op_code.2) { 218 | ("LOAD_CONST", Some(consti)) => { 219 | // println!("Loading const at index: {}", consti); 220 | let curr_frame = self.curr_frame(); 221 | curr_frame.stack.push(Rc::new(curr_frame.code.co_consts[consti].clone())); 222 | None 223 | }, 224 | 225 | // TODO: universal stack element type 226 | ("LOAD_CONST", None) => { 227 | // println!("Loading const at index: {}", consti); 228 | self.curr_frame().stack.push(Rc::new(NativeType::NoneType)); 229 | None 230 | }, 231 | ("POP_TOP", None) => { 232 | self.curr_frame().stack.pop(); 233 | None 234 | }, 235 | ("LOAD_FAST", Some(var_num)) => { 236 | // println!("Loading const at index: {}", consti); 237 | let curr_frame = self.curr_frame(); 238 | let ref name = curr_frame.code.co_varnames[var_num]; 239 | curr_frame.stack.push(curr_frame.locals.get::(name).unwrap().clone()); 240 | None 241 | }, 242 | ("STORE_NAME", Some(namei)) => { 243 | // println!("Loading const at index: {}", consti); 244 | let curr_frame = self.curr_frame(); 245 | curr_frame.locals.insert(curr_frame.code.co_names[namei].clone(), curr_frame.stack.pop().unwrap().clone()); 246 | None 247 | }, 248 | ("LOAD_NAME", Some(namei)) => { 249 | // println!("Loading const at index: {}", consti); 250 | let curr_frame = self.curr_frame(); 251 | if let Some(code) = curr_frame.locals.get::(&curr_frame.code.co_names[namei]) { 252 | curr_frame.stack.push(code.clone()); 253 | } 254 | else { 255 | panic!("Can't find symbol {:?} in the current frame", &curr_frame.code.co_names[namei]); 256 | } 257 | None 258 | }, 259 | ("LOAD_GLOBAL", Some(namei)) => { 260 | // We need to load the underlying value the name points to, but stuff like 261 | // AssertionError is in the names right after compile, so we load the string 262 | // instead for now 263 | let curr_frame = self.curr_frame(); 264 | let name = &curr_frame.code.co_names[namei]; 265 | curr_frame.stack.push(curr_frame.globals.get::(name).unwrap().clone()); 266 | None 267 | }, 268 | 269 | ("BUILD_LIST", Some(count)) => { 270 | let curr_frame = self.curr_frame(); 271 | let mut vec = vec!(); 272 | for _ in 0..count { 273 | vec.push((*curr_frame.stack.pop().unwrap()).clone()); 274 | } 275 | vec.reverse(); 276 | curr_frame.stack.push(Rc::new(NativeType::List(RefCell::new(vec)))); 277 | None 278 | }, 279 | 280 | ("BUILD_SLICE", Some(count)) => { 281 | let curr_frame = self.curr_frame(); 282 | assert!(count == 2 || count == 3); 283 | let mut vec = vec!(); 284 | for _ in 0..count { 285 | vec.push(curr_frame.stack.pop().unwrap()); 286 | } 287 | vec.reverse(); 288 | let mut out:Vec> = vec.into_iter().map(|x| match *x { 289 | NativeType::Int(n) => Some(n), 290 | NativeType::NoneType => None, 291 | _ => panic!("Expect Int or None as BUILD_SLICE arguments, got {:?}", x), 292 | }).collect(); 293 | 294 | if out.len() == 2 { 295 | out.push(None); 296 | } 297 | assert!(out.len() == 3); 298 | // TODO: assert the stop start and step are NativeType::Int 299 | // See https://users.rust-lang.org/t/how-do-you-assert-enums/1187/8 300 | curr_frame.stack.push(Rc::new(NativeType::Slice(out[0], out[1], out[2]))); 301 | None 302 | }, 303 | 304 | ("GET_ITER", None) => { 305 | let curr_frame = self.curr_frame(); 306 | let tos = curr_frame.stack.pop().unwrap(); 307 | let iter = match *tos { 308 | //TODO: is this clone right? 309 | // Return a Iterator instead vvv 310 | NativeType::Tuple(ref vec) => NativeType::Iter(vec.clone()), 311 | NativeType::List(ref vec) => NativeType::Iter(vec.borrow().clone()), 312 | _ => panic!("TypeError: object is not iterable") 313 | }; 314 | curr_frame.stack.push(Rc::new(iter)); 315 | None 316 | }, 317 | 318 | ("FOR_ITER", Some(delta)) => { 319 | // This function should be rewrote to use Rust native iterator 320 | let curr_frame = self.curr_frame(); 321 | let tos = curr_frame.stack.pop().unwrap(); 322 | let result = match *tos { 323 | NativeType::Iter(ref v) => { 324 | if v.len() > 0 { 325 | Some(v.clone()) // Unnessary clone here 326 | } 327 | else { 328 | None 329 | } 330 | } 331 | _ => panic!("FOR_ITER: Not an iterator") 332 | }; 333 | if let Some(vec) = result { 334 | let (first, rest) = vec.split_first().unwrap(); 335 | // Unnessary clone here 336 | curr_frame.stack.push(Rc::new(NativeType::Iter(rest.to_vec()))); 337 | curr_frame.stack.push(Rc::new(first.clone())); 338 | } 339 | else { 340 | // Iterator was already poped in the first line of this function 341 | let last_offset = curr_frame.get_bytecode_offset().unwrap(); 342 | curr_frame.lasti = curr_frame.labels.get(&(last_offset + delta)).unwrap().clone(); 343 | 344 | } 345 | None 346 | }, 347 | 348 | ("COMPARE_OP", Some(cmp_op_i)) => { 349 | let curr_frame = self.curr_frame(); 350 | let v1 = curr_frame.stack.pop().unwrap(); 351 | let v2 = curr_frame.stack.pop().unwrap(); 352 | match CMP_OP[cmp_op_i] { 353 | // To avoid branch explotion, use an array of callables instead 354 | "==" => { 355 | match (v1.deref(), v2.deref()) { 356 | (&NativeType::Int(ref v1i), &NativeType::Int(ref v2i)) => { 357 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2i == v1i))); 358 | }, 359 | (&NativeType::Float(ref v1f), &NativeType::Float(ref v2f)) => { 360 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2f == v1f))); 361 | }, 362 | (&NativeType::Str(ref v1s), &NativeType::Str(ref v2s)) => { 363 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2s == v1s))); 364 | }, 365 | (&NativeType::Int(ref v1i), &NativeType::Float(ref v2f)) => { 366 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2f == &(*v1i as f64)))); 367 | }, 368 | (&NativeType::List(ref l1), &NativeType::List(ref l2)) => { 369 | curr_frame.stack.push(Rc::new(NativeType::Boolean(l2 == l1))); 370 | }, 371 | _ => panic!("TypeError in COMPARE_OP: can't compare {:?} with {:?}", v1, v2) 372 | }; 373 | } 374 | ">" => { 375 | match (v1.deref(), v2.deref()) { 376 | (&NativeType::Int(ref v1i), &NativeType::Int(ref v2i)) => { 377 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2i < v1i))); 378 | }, 379 | (&NativeType::Float(ref v1f), &NativeType::Float(ref v2f)) => { 380 | curr_frame.stack.push(Rc::new(NativeType::Boolean(v2f < v1f))); 381 | }, 382 | _ => panic!("TypeError in COMPARE_OP") 383 | }; 384 | } 385 | _ => panic!("Unimplemented COMPARE_OP operator") 386 | 387 | } 388 | None 389 | 390 | }, 391 | ("POP_JUMP_IF_TRUE", Some(ref target)) => { 392 | let curr_frame = self.curr_frame(); 393 | let v = curr_frame.stack.pop().unwrap(); 394 | if *v == NativeType::Boolean(true) { 395 | curr_frame.lasti = curr_frame.labels.get(target).unwrap().clone(); 396 | } 397 | None 398 | 399 | } 400 | ("POP_JUMP_IF_FALSE", Some(ref target)) => { 401 | let curr_frame = self.curr_frame(); 402 | let v = curr_frame.stack.pop().unwrap(); 403 | if *v == NativeType::Boolean(false) { 404 | curr_frame.lasti = curr_frame.labels.get(target).unwrap().clone(); 405 | } 406 | None 407 | 408 | } 409 | ("JUMP_FORWARD", Some(ref delta)) => { 410 | let curr_frame = self.curr_frame(); 411 | let last_offset = curr_frame.get_bytecode_offset().unwrap(); 412 | curr_frame.lasti = curr_frame.labels.get(&(last_offset + delta)).unwrap().clone(); 413 | None 414 | }, 415 | ("JUMP_ABSOLUTE", Some(ref target)) => { 416 | let curr_frame = self.curr_frame(); 417 | curr_frame.lasti = curr_frame.labels.get(target).unwrap().clone(); 418 | None 419 | }, 420 | ("BREAK_LOOP", None) => { 421 | // Do we still need to return the why if we use unwind from jsapy? 422 | self.unwind("break".to_string()); 423 | None //? 424 | }, 425 | ("RAISE_VARARGS", Some(argc)) => { 426 | let curr_frame = self.curr_frame(); 427 | // let (exception, params, traceback) = match argc { 428 | let exception = match argc { 429 | 1 => curr_frame.stack.pop().unwrap(), 430 | 0 | 2 | 3 => panic!("Not implemented!"), 431 | _ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3") 432 | }; 433 | panic!("{:?}", exception); 434 | } 435 | ("INPLACE_ADD", None) => { 436 | let curr_frame = self.curr_frame(); 437 | let tos = curr_frame.stack.pop().unwrap(); 438 | let tos1 = curr_frame.stack.pop().unwrap(); 439 | match (tos.deref(), tos1.deref()) { 440 | (&NativeType::Int(ref tosi), &NativeType::Int(ref tos1i)) => { 441 | curr_frame.stack.push(Rc::new(NativeType::Int(tos1i + tosi))); 442 | }, 443 | _ => panic!("TypeError in BINARY_ADD") 444 | } 445 | None 446 | }, 447 | 448 | ("STORE_SUBSCR", None) => { 449 | let curr_frame = self.curr_frame(); 450 | let tos = curr_frame.stack.pop().unwrap(); 451 | let tos1 = curr_frame.stack.pop().unwrap(); 452 | let tos2 = curr_frame.stack.pop().unwrap(); 453 | match (tos1.deref(), tos.deref()) { 454 | (&NativeType::List(ref refl), &NativeType::Int(index)) => { 455 | refl.borrow_mut()[index as usize] = (*tos2).clone(); 456 | }, 457 | (&NativeType::Str(_), &NativeType::Int(_)) => { 458 | // TODO: raise TypeError: 'str' object does not support item assignment 459 | panic!("TypeError: 'str' object does not support item assignment") 460 | }, 461 | _ => panic!("TypeError in STORE_SUBSCR") 462 | } 463 | curr_frame.stack.push(tos1); 464 | None 465 | }, 466 | 467 | ("BINARY_ADD", None) => { 468 | let curr_frame = self.curr_frame(); 469 | let v1 = curr_frame.stack.pop().unwrap(); 470 | let v2 = curr_frame.stack.pop().unwrap(); 471 | match (v1.deref(), v2.deref()) { 472 | (&NativeType::Int(ref v1i), &NativeType::Int(ref v2i)) => { 473 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i + v1i))); 474 | } 475 | (&NativeType::Float(ref v1f), &NativeType::Int(ref v2i)) => { 476 | curr_frame.stack.push(Rc::new(NativeType::Float(*v2i as f64 + v1f))); 477 | } 478 | (&NativeType::Int(ref v1i), &NativeType::Float(ref v2f)) => { 479 | curr_frame.stack.push(Rc::new(NativeType::Float(v2f + *v1i as f64))); 480 | } 481 | (&NativeType::Float(ref v1f), &NativeType::Float(ref v2f)) => { 482 | curr_frame.stack.push(Rc::new(NativeType::Float(v2f + v1f))); 483 | } 484 | (&NativeType::Str(ref str1), &NativeType::Str(ref str2)) => { 485 | curr_frame.stack.push(Rc::new(NativeType::Str(format!("{}{}", str2, str1)))); 486 | } 487 | (&NativeType::List(ref l1), &NativeType::List(ref l2)) => { 488 | let mut new_l = l2.clone(); 489 | // TODO: remove unnessary copy 490 | new_l.borrow_mut().append(&mut l1.borrow().clone()); 491 | curr_frame.stack.push(Rc::new(NativeType::List(new_l))); 492 | 493 | } 494 | _ => panic!("TypeError in BINARY_ADD") 495 | } 496 | None 497 | }, 498 | ("BINARY_POWER", None) => { 499 | let curr_frame = self.curr_frame(); 500 | let v1 = curr_frame.stack.pop().unwrap(); 501 | let v2 = curr_frame.stack.pop().unwrap(); 502 | match (v1.deref(), v2.deref()) { 503 | (&NativeType::Int(v1i), &NativeType::Int(v2i)) => { 504 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i.pow(v1i as u32)))); 505 | } 506 | (&NativeType::Float(v1f), &NativeType::Int(v2i)) => { 507 | curr_frame.stack.push(Rc::new(NativeType::Float((v2i as f64).powf(v1f)))); 508 | } 509 | (&NativeType::Int(v1i), &NativeType::Float(v2f)) => { 510 | curr_frame.stack.push(Rc::new(NativeType::Float(v2f.powi(v1i)))); 511 | } 512 | (&NativeType::Float(v1f), &NativeType::Float(v2f)) => { 513 | curr_frame.stack.push(Rc::new(NativeType::Float(v2f.powf(v1f)))); 514 | } 515 | _ => panic!("TypeError in BINARY_POWER") 516 | } 517 | None 518 | }, 519 | ("BINARY_MULTIPLY", None) => { 520 | let curr_frame = self.curr_frame(); 521 | let v1 = curr_frame.stack.pop().unwrap(); 522 | let v2 = curr_frame.stack.pop().unwrap(); 523 | match (v1.deref(), v2.deref()) { 524 | (&NativeType::Int(v1i), &NativeType::Int(v2i)) => { 525 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i * v1i))); 526 | }, 527 | /* 528 | (NativeType::Float(v1f), NativeType::Int(v2i)) => { 529 | curr_frame.stack.push(NativeType::Float((v2i as f64) * v1f)); 530 | }, 531 | (NativeType::Int(v1i), NativeType::Float(v2f)) => { 532 | curr_frame.stack.push(NativeType::Float(v2f * (v1i as f64))); 533 | }, 534 | (NativeType::Float(v1f), NativeType::Float(v2f)) => { 535 | curr_frame.stack.push(NativeType::Float(v2f * v1f)); 536 | }, 537 | */ 538 | //TODO: String multiply 539 | _ => panic!("TypeError in BINARY_MULTIPLY") 540 | } 541 | None 542 | }, 543 | ("BINARY_TRUE_DIVIDE", None) => { 544 | let curr_frame = self.curr_frame(); 545 | let v1 = curr_frame.stack.pop().unwrap(); 546 | let v2 = curr_frame.stack.pop().unwrap(); 547 | match (v1.deref(), v2.deref()) { 548 | (&NativeType::Int(v1i), &NativeType::Int(v2i)) => { 549 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i / v1i))); 550 | }, 551 | _ => panic!("TypeError in BINARY_DIVIDE") 552 | } 553 | None 554 | }, 555 | ("BINARY_MODULO", None) => { 556 | let curr_frame = self.curr_frame(); 557 | let v1 = curr_frame.stack.pop().unwrap(); 558 | let v2 = curr_frame.stack.pop().unwrap(); 559 | match (v1.deref(), v2.deref()) { 560 | (&NativeType::Int(v1i), &NativeType::Int(v2i)) => { 561 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i % v1i))); 562 | }, 563 | _ => panic!("TypeError in BINARY_MODULO") 564 | } 565 | None 566 | }, 567 | ("BINARY_SUBTRACT", None) => { 568 | let curr_frame = self.curr_frame(); 569 | let v1 = curr_frame.stack.pop().unwrap(); 570 | let v2 = curr_frame.stack.pop().unwrap(); 571 | match (v1.deref(), v2.deref()) { 572 | (&NativeType::Int(v1i), &NativeType::Int(v2i)) => { 573 | curr_frame.stack.push(Rc::new(NativeType::Int(v2i - v1i))); 574 | }, 575 | _ => panic!("TypeError in BINARY_SUBSTRACT") 576 | } 577 | None 578 | }, 579 | 580 | ("BINARY_SUBSCR", None) => { 581 | let curr_frame = self.curr_frame(); 582 | let tos = curr_frame.stack.pop().unwrap(); 583 | let tos1 = curr_frame.stack.pop().unwrap(); 584 | debug!("tos: {:?}, tos1: {:?}", tos, tos1); 585 | match (tos1.deref(), tos.deref()) { 586 | (&NativeType::List(ref l), &NativeType::Int(ref index)) => { 587 | let pos_index = (index + l.borrow().len() as i32) % l.borrow().len() as i32; 588 | curr_frame.stack.push(Rc::new(l.borrow()[pos_index as usize].clone())) 589 | }, 590 | (&NativeType::List(ref l), &NativeType::Slice(ref opt_start, ref opt_stop, ref opt_step)) => { 591 | let start = match opt_start { 592 | &Some(start) => ((start + l.borrow().len() as i32) % l.borrow().len() as i32) as usize, 593 | &None => 0, 594 | }; 595 | let stop = match opt_stop { 596 | &Some(stop) => ((stop + l.borrow().len() as i32) % l.borrow().len() as i32) as usize, 597 | &None => l.borrow().len() as usize, 598 | }; 599 | let step = match opt_step { 600 | //Some(step) => step as usize, 601 | &None => 1 as usize, 602 | _ => unimplemented!(), 603 | }; 604 | // TODO: we could potentially avoid this copy and use slice 605 | curr_frame.stack.push(Rc::new(NativeType::List(RefCell::new(l.borrow()[start..stop].to_vec())))); 606 | }, 607 | (&NativeType::Tuple(ref t), &NativeType::Int(ref index)) => curr_frame.stack.push(Rc::new(t[*index as usize].clone())), 608 | (&NativeType::Str(ref s), &NativeType::Int(ref index)) => { 609 | let idx = (index + s.len() as i32) % s.len() as i32; 610 | curr_frame.stack.push(Rc::new(NativeType::Str(s.chars().nth(idx as usize).unwrap().to_string()))); 611 | }, 612 | (&NativeType::Str(ref s), &NativeType::Slice(ref opt_start, ref opt_stop, ref opt_step)) => { 613 | let start = match opt_start { 614 | &Some(start) if start > s.len() as i32 => s.len(), 615 | &Some(start) if start <= s.len() as i32 => ((start + s.len() as i32) % s.len() as i32) as usize, 616 | &Some(_) => panic!("Bad start index for string slicing"), 617 | &Some(start) => ((start + s.len() as i32) % s.len() as i32) as usize, 618 | &None => 0, 619 | }; 620 | let stop = match opt_stop { 621 | &Some(stop) if stop > s.len() as i32 => s.len(), 622 | &Some(stop) if stop <= s.len() as i32 => ((stop + s.len() as i32) % s.len() as i32) as usize, // Do we need this modding? 623 | &Some(_) => panic!("Bad stop index for string slicing"), 624 | &None => s.len() as usize, 625 | }; 626 | let step = match opt_step { 627 | //Some(step) => step as usize, 628 | &None => 1 as usize, 629 | _ => unimplemented!(), 630 | }; 631 | curr_frame.stack.push(Rc::new(NativeType::Str(s[start..stop].to_string()))); 632 | }, 633 | // TODO: implement other Slice possibilities 634 | _ => panic!("TypeError: indexing type {:?} with index {:?} is not supported (yet?)", tos1, tos) 635 | }; 636 | None 637 | }, 638 | ("ROT_TWO", None) => { 639 | let curr_frame = self.curr_frame(); 640 | let tos = curr_frame.stack.pop().unwrap(); 641 | let tos1 = curr_frame.stack.pop().unwrap(); 642 | curr_frame.stack.push(tos); 643 | curr_frame.stack.push(tos1); 644 | None 645 | } 646 | ("UNARY_NEGATIVE", None) => { 647 | let curr_frame = self.curr_frame(); 648 | let v = curr_frame.stack.pop().unwrap(); 649 | match v.deref() { 650 | &NativeType::Int(v1i) => { 651 | curr_frame.stack.push(Rc::new(NativeType::Int(-v1i))); 652 | }, 653 | _ => panic!("TypeError in UINARY_NEGATIVE") 654 | } 655 | None 656 | }, 657 | ("UNARY_POSITIVE", None) => { 658 | let curr_frame = self.curr_frame(); 659 | let v = curr_frame.stack.pop().unwrap(); 660 | // Any case that is not just push back? 661 | curr_frame.stack.push(v); 662 | None 663 | }, 664 | ("PRINT_ITEM", None) => { 665 | let curr_frame = self.curr_frame(); 666 | // TODO: Print without the (...) 667 | println!("{:?}", curr_frame.stack.pop().unwrap()); 668 | None 669 | }, 670 | ("PRINT_NEWLINE", None) => { 671 | print!("\n"); 672 | None 673 | }, 674 | ("MAKE_FUNCTION", Some(argc)) => { 675 | // https://docs.python.org/3.4/library/dis.html#opcode-MAKE_FUNCTION 676 | let curr_frame = self.curr_frame(); 677 | let qualified_name = curr_frame.stack.pop().unwrap(); 678 | let code_obj = match curr_frame.stack.pop().unwrap().deref() { 679 | &NativeType::Code(ref code) => code.clone(), 680 | _ => panic!("Second item on the stack should be a code object") 681 | }; 682 | // pop argc arguments 683 | // argument: name, args, globals 684 | let func = Function::new(code_obj); 685 | curr_frame.stack.push(Rc::new(NativeType::Function(func))); 686 | None 687 | }, 688 | ("CALL_FUNCTION", Some(argc)) => { 689 | let kw_count = (argc >> 8) as u8; 690 | let pos_count = (argc & 0xFF) as u8; 691 | // Pop the arguments based on argc 692 | let mut kw_args = HashMap::new(); 693 | let mut pos_args = Vec::new(); 694 | { 695 | let curr_frame = self.curr_frame(); 696 | for _ in 0..kw_count { 697 | let native_val = curr_frame.stack.pop().unwrap(); 698 | let native_key = curr_frame.stack.pop().unwrap(); 699 | if let (ref val, &NativeType::Str(ref key)) = (native_val, native_key.deref()) { 700 | 701 | kw_args.insert(key.clone(), val.clone()); 702 | } 703 | else { 704 | panic!("Incorrect type found while building keyword argument list") 705 | } 706 | } 707 | for _ in 0..pos_count { 708 | pos_args.push(curr_frame.stack.pop().unwrap()); 709 | } 710 | } 711 | let locals = { 712 | // FIXME: no clone here 713 | self.curr_frame().locals.clone() 714 | }; 715 | 716 | let func = { 717 | match self.curr_frame().stack.pop().unwrap().deref() { 718 | &NativeType::Function(ref func) => { 719 | // pop argc arguments 720 | // argument: name, args, globals 721 | // build the callargs hashmap 722 | pos_args.reverse(); 723 | let mut callargs = HashMap::new(); 724 | for (name, val) in func.code.co_varnames.iter().zip(pos_args) { 725 | callargs.insert(name.to_string(), val); 726 | } 727 | // merge callargs with kw_args 728 | let return_value = { 729 | let frame = self.make_frame(func.code.clone(), callargs, Some(locals)); 730 | self.run_frame(frame) 731 | }; 732 | self.curr_frame().stack.push(Rc::new(return_value)); 733 | }, 734 | &NativeType::NativeFunction(func) => { 735 | pos_args.reverse(); 736 | let return_value = func(pos_args); 737 | self.curr_frame().stack.push(Rc::new(return_value)); 738 | }, 739 | _ => panic!("The item on the stack should be a code object") 740 | } 741 | }; 742 | None 743 | }, 744 | ("RETURN_VALUE", None) => { 745 | // Hmmm... what is this used? 746 | // I believe we need to push this to the next frame 747 | self.curr_frame().return_value = (*self.curr_frame().stack.pop().unwrap()).clone(); 748 | Some("return".to_string()) 749 | }, 750 | ("SETUP_LOOP", Some(delta)) => { 751 | let curr_frame = self.curr_frame(); 752 | let curr_offset = curr_frame.get_bytecode_offset().unwrap(); 753 | curr_frame.blocks.push(Block { 754 | block_type: "loop".to_string(), 755 | handler: *curr_frame.labels.get(&(curr_offset + delta)).unwrap(), 756 | }); 757 | None 758 | }, 759 | ("POP_BLOCK", None) => { 760 | self.curr_frame().blocks.pop(); 761 | None 762 | } 763 | ("SetLineno", _) | ("LABEL", _)=> { 764 | // Skip 765 | None 766 | }, 767 | (name, _) => { 768 | panic!("Unrecongnizable op code: {}", name); 769 | } 770 | } // end match 771 | } // end dispatch function 772 | } 773 | 774 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 775 | pub struct PyCodeObject { 776 | co_consts: Vec, 777 | co_names: Vec, 778 | co_code: Vec<(usize, String, Option)>, //size, name, args 779 | co_varnames: Vec, 780 | } 781 | 782 | 783 | /* 784 | fn parse_native_type(val_str: &str) -> Result { 785 | // println!("{:?}", val_str); 786 | match val_str { 787 | "None" => Ok(NativeType::NoneType), 788 | "True" => Ok(NativeType::Boolean(true)), 789 | "False" => Ok(NativeType::Boolean(false)), 790 | _ => { 791 | if let Ok(int) = val_str.parse::() { 792 | return Ok(NativeType::Int(int)) 793 | } 794 | 795 | if let Ok(float) = val_str.parse::() { 796 | return Ok(NativeType::Float(float)) 797 | } 798 | 799 | if val_str.starts_with("\'") && val_str.ends_with("\'") { 800 | return Ok(NativeType::Str(val_str[1..val_str.len()-1].to_string())) 801 | } 802 | 803 | if val_str.starts_with("u\'") && val_str.ends_with("\'") { 804 | return Ok(NativeType::Unicode(val_str[2..val_str.len()-1].to_string())) 805 | } 806 | 807 | if val_str.starts_with("(") && val_str.ends_with(")") { 808 | return Ok(NativeType::Str(val_str[1..val_str.len()-1].to_string())) 809 | } 810 | 811 | Err(()) 812 | } 813 | 814 | } 815 | } 816 | 817 | fn parse_bytecode(s: &str) -> Code { 818 | let lines: Vec<&str> = s.split('\n').collect(); 819 | 820 | let (metadata, ops) = lines.split_at(2); 821 | // Parsing the first line CONSTS 822 | let consts_str: &str = metadata[0]; // line 0 is empty 823 | let values_str = &consts_str[("CONSTS: (".len())..(consts_str.len()-1)]; 824 | let values: Vec<&str> = values_str.split(",").collect(); 825 | // We need better type definition here 826 | let consts: Vec= values.into_iter() 827 | .map(|x| x.trim()) 828 | .filter(|x| x.len() > 0) 829 | .map(|x| parse_native_type(x).unwrap()) 830 | .collect(); 831 | 832 | // Parsing the second line NAMES 833 | let names_str: &str = metadata[1]; // line 0 is empty 834 | let values_str = &names_str[("NAMES: (".len())..(names_str.len()-1)]; 835 | let values: Vec<&str> = values_str.split(",").collect(); 836 | // We are assuming the first and last chars are \' 837 | let names: Vec<&str>= values.into_iter().map(|x| x.trim()) 838 | .filter(|x| x.len() > 0) 839 | .map(|x| &x[1..(x.len()-1)]).collect(); 840 | 841 | // Parsing the op_codes 842 | let op_codes: Vec<(&str, Option)>= ops.into_iter() 843 | .map(|x| x.trim()) 844 | .filter(|x| x.len() > 0) 845 | .map(|x| { 846 | let op: Vec<&str> = x.split(", ").collect(); 847 | // println!("{:?}", op); 848 | (op[0], op[1].parse::().ok()) 849 | }).collect(); 850 | 851 | 852 | Code { 853 | consts: consts, 854 | op_codes: op_codes, 855 | names: names, 856 | } 857 | } 858 | */ 859 | fn main() { 860 | env_logger::init().unwrap(); 861 | // TODO: read this from args 862 | let args: Vec = env::args().collect(); 863 | let filename = &args[1]; 864 | 865 | let mut f = File::open(filename).unwrap(); 866 | // println!("Read file"); 867 | let mut s = String::new(); 868 | f.read_to_string(&mut s).unwrap(); 869 | // println!("Read string"); 870 | let code: PyCodeObject = match serde_json::from_str(&s) { 871 | Ok(c) => c, 872 | Err(_) => panic!("Fail to parse the bytecode") 873 | }; 874 | 875 | let mut vm = VirtualMachine::new(); 876 | vm.run_code(code); 877 | // println!("Done"); 878 | } 879 | 880 | /* 881 | #[test] 882 | fn test_parse_native_type() { 883 | 884 | assert_eq!(NativeType::NoneType, parse_native_type("None").unwrap()); 885 | assert_eq!(NativeType::Boolean(true), parse_native_type("True").unwrap()); 886 | assert_eq!(NativeType::Boolean(false), parse_native_type("False").unwrap()); 887 | assert_eq!(NativeType::Int(3), parse_native_type("3").unwrap()); 888 | assert_eq!(NativeType::Float(3.0), parse_native_type("3.0").unwrap()); 889 | assert_eq!(NativeType::Float(3.5), parse_native_type("3.5").unwrap()); 890 | assert_eq!(NativeType::Str("foo".to_string()), parse_native_type("\'foo\'").unwrap()); 891 | assert_eq!(NativeType::Unicode("foo".to_string()), parse_native_type("u\'foo\'").unwrap()); 892 | } 893 | 894 | #[test] 895 | fn test_parse_bytecode() { 896 | 897 | let input = "CONSTS: (1, None, 2) 898 | NAMES: ('a', 'b') 899 | SetLineno, 1 900 | LOAD_CONST, 2 901 | PRINT_ITEM, None 902 | PRINT_NEWLINE, None 903 | LOAD_CONST, None 904 | RETURN_VALUE, None 905 | "; 906 | let expected = Code { // Fill me with a more sensible data 907 | consts: vec![NativeType::Int(1), NativeType::NoneType, NativeType::Int(2)], 908 | names: vec!["a", "b"], 909 | op_codes: vec![ 910 | ("SetLineno", Some(1)), 911 | ("LOAD_CONST", Some(2)), 912 | ("PRINT_ITEM", None), 913 | ("PRINT_NEWLINE", None), 914 | ("LOAD_CONST", None), 915 | ("RETURN_VALUE", None) 916 | ] 917 | }; 918 | 919 | assert_eq!(expected, parse_bytecode(input)); 920 | } 921 | 922 | #[test] 923 | fn test_single_const_tuple() { 924 | let input = "CONSTS: (None,) 925 | NAMES: () 926 | SetLineno, 1 927 | LOAD_CONST, 0 928 | RETURN_VALUE, None 929 | "; 930 | let expected = Code { // Fill me with a more sensible data 931 | consts: vec![NativeType::NoneType], 932 | names: vec![], 933 | op_codes: vec![ 934 | ("SetLineno", Some(1)), 935 | ("LOAD_CONST", Some(0)), 936 | ("RETURN_VALUE", None) 937 | ] 938 | }; 939 | 940 | assert_eq!(expected, parse_bytecode(input)); 941 | } 942 | 943 | #[test] 944 | fn test_vm() { 945 | 946 | let code = PyCodeObject { 947 | co_consts: vec![NativeType::Int(1), NativeType::NoneType, NativeType::Int(2)], 948 | co_names: vec![], 949 | co_code: vec![ 950 | (3, "LOAD_CONST".to_string(), Some(2)), 951 | (1, "PRINT_ITEM".to_string(), None), 952 | (1, "PRINT_NEWLINE".to_string(), None), 953 | (3, "LOAD_CONST".to_string(), None), 954 | (1, "RETURN_VALUE".to_string(), None) 955 | ] 956 | }; 957 | let mut vm = VirtualMachine::new(); 958 | assert_eq!((), vm.exec(&code)); 959 | } 960 | 961 | 962 | #[test] 963 | fn test_parse_jsonbytecode() { 964 | 965 | let input = "{\"co_consts\":[{\"Int\":1},\"NoneType\",{\"Int\":2}],\"co_names\":[\"print\"],\"co_code\":[[3,\"LOAD_CONST\",2],[1,\"PRINT_ITEM\",null],[1,\"PRINT_NEWLINE\",null],[3,\"LOAD_CONST\",null],[1,\"RETURN_VALUE\",null]]}"; 966 | // let input = "{\"co_names\": [\"print\"], \"co_code\": [[\"LOAD_CONST\", 0], [\"LOAD_CONST\", 0], [\"COMPARE_OP\", 2], [\"POP_JUMP_IF_FALSE\", 25], [\"LOAD_NAME\", 0], [\"LOAD_CONST\", 1], [\"CALL_FUNCTION\", 1], [\"POP_TOP\", null], [\"JUMP_FORWARD\", 10], [\"LOAD_NAME\", 0], [\"LOAD_CONST\", 2], [\"CALL_FUNCTION\", 1], [\"POP_TOP\", null], [\"LOAD_CONST\", 0], [\"LOAD_CONST\", 3], [\"COMPARE_OP\", 2], [\"POP_JUMP_IF_FALSE\", 60], [\"LOAD_NAME\", 0], [\"LOAD_CONST\", 1], [\"CALL_FUNCTION\", 1], [\"POP_TOP\", null], [\"JUMP_FORWARD\", 10], [\"LOAD_NAME\", 0], [\"LOAD_CONST\", 2], [\"CALL_FUNCTION\", 1], [\"POP_TOP\", null], [\"LOAD_CONST\", 4], [\"RETURN_VALUE\", null]], \"co_consts\": [{\"Int\": 1}, {\"Str\": \"equal\"}, {\"Str\": \"not equal\"}, {\"Int\": 2}, {\"NoneType\": null}]}"; 967 | 968 | let expected = PyCodeObject { // Fill me with a more sensible data 969 | co_consts: vec![NativeType::Int(1), NativeType::NoneType, NativeType::Int(2)], 970 | co_names: vec!["print".to_string()], 971 | co_code: vec![ 972 | (3, "LOAD_CONST".to_string(), Some(2)), 973 | (1, "PRINT_ITEM".to_string(), None), 974 | (1, "PRINT_NEWLINE".to_string(), None), 975 | (3, "LOAD_CONST".to_string(), None), 976 | (1, "RETURN_VALUE".to_string(), None) 977 | ] 978 | }; 979 | println!("{}", serde_json::to_string(&expected).unwrap()); 980 | 981 | let deserialized: PyCodeObject = serde_json::from_str(&input).unwrap(); 982 | assert_eq!(expected, deserialized) 983 | } 984 | */ 985 | 986 | #[test] 987 | fn test_tuple_serialization(){ 988 | let tuple = NativeType::Tuple(vec![NativeType::Int(1),NativeType::Int(2)]); 989 | println!("{}", serde_json::to_string(&tuple).unwrap()); 990 | } 991 | -------------------------------------------------------------------------------- /compile_code.py: -------------------------------------------------------------------------------- 1 | import bytecode 2 | import sys 3 | import json 4 | import types 5 | 6 | 7 | class CodeEncoder(json.JSONEncoder): 8 | def default(self, obj): 9 | if (isinstance(obj, types.CodeType)): 10 | return serialize_code(obj) 11 | return json.JSONEncoder.default(self, obj) 12 | 13 | def serialize_code(code): 14 | c = bytecode.Bytecode().from_code(code).to_concrete_bytecode() 15 | return ( 16 | { 17 | "co_consts": consts_to_rust_enum(c.consts), 18 | "co_names": c.names, 19 | "co_name": c.name, 20 | "co_code": parse_co_code_to_str(c), 21 | "co_varnames": c.varnames 22 | } 23 | ) 24 | 25 | 26 | def consts_to_rust_enum(consts): 27 | def capitalize_first(s): 28 | return s[0].upper() + s[1:] 29 | 30 | def const_to_rust_enum(const): 31 | if type(const).__name__ == "tuple": 32 | return {capitalize_first(str(type(const).__name__)): list(map(const_to_rust_enum, const))} 33 | else: 34 | return {capitalize_first(str(type(const).__name__)): const} 35 | return list(map(const_to_rust_enum, consts)) 36 | 37 | 38 | def parse_co_code_to_str(c): 39 | return list( 40 | map(lambda op: (op.size, op.name, op.arg if op.arg != bytecode.UNSET else None), 41 | c) 42 | ) 43 | 44 | 45 | def main(): 46 | 47 | filename = sys.argv[1] 48 | with open(filename, 'rU') as f: 49 | code = f.read() 50 | 51 | code = compile(code, filename, "exec") 52 | 53 | print(CodeEncoder().encode(code)) 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /docs/builtins.md: -------------------------------------------------------------------------------- 1 | Byterun 2 | 3 | * Builtins are exposted to frame.f_builtins 4 | * f_builtins is assigned during frame creation, 5 | self.f_builtins = f_locals['__builtins__'] 6 | if hasattr(self.f_builtins, '__dict__'): 7 | self.f_builtins = self.f_builtins.__dict__ 8 | * f_locals has a __`____builtins___` field which is directly the `__builtins__` module 9 | 10 | 11 | Jaspy 12 | 13 | * The `module()` function creates either a NativeModule or PythonModule 14 | * The objects in the module are PyType.native 15 | * The function call is abstracted as a `call` function, which handles different 16 | 17 | * IMPORT_NAME depends on `__import__()` in builtins 18 | 19 | TODO: 20 | 21 | * Implement a new type NativeFunction 22 | * Wrap a function pointer in NativeFunction 23 | * Refactor the CALL_FUNCTION case so it can call both python function and native function 24 | * During frame creation, force push a nativefunction `print` into the namespace 25 | * Modify LOAD_* so they can search for names in builtins 26 | 27 | * Create a module type 28 | * In VM initialization, load the builtins module into locals 29 | * During frame creation, create a field that conatins the builtins dict 30 | 31 | -------------------------------------------------------------------------------- /docs/compat-test.md: -------------------------------------------------------------------------------- 1 | https://wiki.python.org/moin/SimplePrograms 2 | Simple HTTP Server 3 | bottle.py 4 | http://pypi-ranking.info/alltime 5 | - simplejson 6 | - pep8 7 | - httplib2 8 | - argparse 9 | 10 | http://learning-python.com/books/lp4e-examples.html 11 | 12 | https://docs.python.org/3/tutorial/introduction.html#numbers 13 | 14 | http://doc.pypy.org/en/latest/getting-started-dev.html#running-pypy-s-unit-tests 15 | 16 | CPython Regression suite 17 | https://github.com/python/cpython/tree/master/Lib/test 18 | -------------------------------------------------------------------------------- /docs/frameTODO.txt: -------------------------------------------------------------------------------- 1 | Create a frame class 2 | change the environment to locals 3 | turn run into run_code -> run_frame 4 | 5 | TEST 6 | 7 | Create a function class 8 | implement MAKE_FUNCTION 9 | implement CALL_FUNCTION 10 | implement RETURN_VALUE 11 | 12 | TEST function 13 | 14 | -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | # TODO: other notes should be put into here 2 | 3 | # getattr() 4 | - Required by this opcode [LOAD_ATTR](https://docs.python.org/3/library/dis.html#opcode-LOAD_ATTR) 5 | - The builtin function: https://docs.python.org/3/library/functions.html?highlight=getattr#getattr 6 | - 7 | 8 | # Memory management 9 | - https://docs.python.org/3.6/c-api/memory.html 10 | 11 | # Bootstraping 12 | - http://doc.pypy.org/en/latest/coding-guide.html#our-runtime-interpreter-is-rpython 13 | - http://www.aosabook.org/en/pypy.html 14 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | Python bytecode: https://docs.python.org/3.4/library/dis.html 2 | Python VM in python: https://github.com/nedbat/byterun 3 | Python VM in JS: https://github.com/koehlma/jaspy 4 | 5 | http://www.skulpt.org/ 6 | -------------------------------------------------------------------------------- /docs/sharing.md: -------------------------------------------------------------------------------- 1 | # Sharing plan 2 | 3 | ## Topics 4 | 5 | * Python architecture 6 | * Compiler -> VM 7 | * Flavors 8 | * Cpython 9 | * Pypy 10 | * Byterun 11 | * JSapy (?) 12 | * Why Rust 13 | * Security 14 | * Learning 15 | * Implementation plan 16 | * VM first 17 | * Compiler second 18 | 19 | * Tools for study 20 | * dis doc 21 | * byterun doc 22 | * bytrun code 23 | * cpython source 24 | 25 | --- 26 | * Python VM 27 | * Stack machine 28 | 29 | * Load add print 30 | * dis 31 | * Interpreter loop 32 | * Python Types 33 | 34 | * Control flow 35 | * Jump 36 | * If 37 | * Loop 38 | 39 | --- 40 | * Function call 41 | * Frame 42 | * Builtins 43 | 44 | -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/car_cutaway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/car_cutaway.jpg -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/electronic_parts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/electronic_parts.jpg -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/ice-cream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/ice-cream.jpg -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/python-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/python-logo.png -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/repo_QR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/repo_QR.png -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/pic/rust-servo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/201708_COSCUP/pic/rust-servo.png -------------------------------------------------------------------------------- /docs/slides/201708_COSCUP/slide.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | ##Python Interpreter in Rust 3 | #### 2017/8/6, COSCUP 4 | #### Shing Lyu 5 | 6 | 7 | ??? 8 | top, middle, bottom 9 | left, center, right 10 | 11 | --- 12 | ### About Me 13 | * 呂行 Shing Lyu 14 | * Mozilla engineer 15 | * Servo team 16 | 17 | ![rust_and_servo](pic/rust-servo.png) 18 | --- 19 | ### Python's architecture 20 | * Interpreted 21 | * Garbage Collected 22 | * Compiler => bytecode => VM 23 | 24 | ![python](pic/python-logo.png) 25 | 26 | --- 27 | background-image: url('pic/ice-cream.jpg') 28 | class: center, middle, bleed, text-bg 29 | # Flavors 30 | 31 | 32 | --- 33 | ### Python Flavors 34 | * CPython (THE python) 35 | * Jython (JVM) 36 | * IronPython (.NET) 37 | * Pypy 38 | * Educational 39 | * Byterun 40 | * Jsapy (JS) 41 | * Brython (Python in browser) 42 | 43 | --- 44 | class: center, middle 45 | 46 | # Why & How? 47 | --- 48 | ### Why rewriting Python in Rust? 49 | * Memory safety (?) 50 | * Learn about Python's internal 51 | * Learn to write Rust from scratch 52 | * FUN! 53 | 54 | --- 55 | ### Implementation strategy 56 | * Mostly follow CPython 3.6 57 | * Focus on the VM first, then the compiler 58 | * Use the Python built-in compiler to generate bytecode 59 | * Focus on learning rather than usability 60 | 61 | --- 62 | ### Milestones 63 | * Basic arithmetics 64 | * Variables 65 | * Control flows (require JUMP) 66 | * Function call (require call stack) 67 | * Built-in functions (require native code) 68 | * Run Python tutorial example code <= We're here 69 | * Exceptions 70 | * GC 71 | * Run popular libraries 72 | 73 | 74 | --- 75 | class: center, middle, bleed, text-bg 76 | background-image: url('pic/car_cutaway.jpg') 77 | # Python Internals 78 | 79 | --- 80 | ### How Python VM works 81 | * Stack machine 82 | * Call stack and frames 83 | * Has a NAMES list and CONSTS list 84 | * Has a STACK as workspace 85 | * Accepts Python bytecode 86 | * `python -m dis source.py` 87 | 88 | --- 89 | 90 | ### A simple Python code 91 | 92 | ``` 93 | #!/usr/bin/env python3 94 | print(1+1) 95 | ``` 96 | 97 | Running `python3 -m dis source.py` gives us 98 | 99 | ``` 100 | 1 0 LOAD_NAME 0 (print) 101 | 3 LOAD_CONST 2 (2) 102 | 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 103 | 9 POP_TOP 104 | 10 LOAD_CONST 1 (None) 105 | 13 RETURN_VALUE 106 | 107 | ``` 108 | 109 | --- 110 | 111 | ### LOAD_NAME "print" 112 | * NAMES = ["print"] 113 | * CONSTS = [None, 2] 114 | * STACK: 115 | 116 | ``` 117 | | | 118 | | print (native code)| 119 | +--------------------+ 120 | ``` 121 | --- 122 | ### LOAD_CONST 2 123 | * NAMES = ["print"] 124 | * CONSTS = [None, 2] 125 | * STACK: 126 | 127 | ``` 128 | | | 129 | | 2 | 130 | | print (native code)| 131 | +--------------------+ 132 | ``` 133 | 134 | --- 135 | 136 | ### CALL_FUNCTION 1 137 | 1. `argument = stack.pop()` (argument == 2) 138 | 2. `function = stack.top()` (function == print) 139 | 3. call `print(2)` 140 | 141 | * NAMES = ["print"] 142 | * CONSTS = [None, 2] 143 | * STACK: 144 | 145 | ``` 146 | | | 147 | | print (native code)| 148 | +--------------------+ 149 | ``` 150 | --- 151 | ### POP_TOP 152 | * NAMES = ["print"] 153 | * CONSTS = [None, 2] 154 | * STACK: 155 | 156 | ``` 157 | | | 158 | | (empty) | 159 | +--------------------+ 160 | ``` 161 | 162 | --- 163 | ### LOAD_CONST None 164 | * NAMES = ["print"] 165 | * CONSTS = [None, 2] 166 | * STACK: 167 | 168 | ``` 169 | | | 170 | | None | 171 | +--------------------+ 172 | ``` 173 | 174 | --- 175 | ### RETURN_VALUE 176 | 177 | (returns top of stack == None) 178 | 179 | --- 180 | 181 | class: center, middle, bleed, text-bg 182 | background-image: url('pic/electronic_parts.jpg') 183 | # Technical Detail 184 | 185 | --- 186 | 187 | ### Bytecode format 188 | * `dis` output format is for human reader 189 | * Implementing a `dis` format parser is a waste of time 190 | * Emit JSON bytecode using the [bytecode](https://pypi.python.org/pypi/bytecode/0.5) module 191 | 192 | ``` 193 | 194 | code = compile(f,...) # Python built-in, return a Code object 195 | 196 | bytecode.Bytecode() 197 | .from_code(code) 198 | .to_concrete_bytecode() 199 | ``` 200 | * Load into Rust using `serde_json` 201 | --- 202 | 203 | ### Types 204 | * Everything is a `PyObject` in CPython 205 | * We'll need that class hierarchy eventually 206 | * Use a Rust `enum` for now 207 | 208 | ``` 209 | pub enum NativeType{ 210 | NoneType, 211 | Boolean(bool), 212 | Int(i32), 213 | Str(String), 214 | Tuple(Vec), 215 | ... 216 | } 217 | ``` 218 | 219 | --- 220 | 221 | ### Testing 222 | * `assert` is essential to for unittests 223 | * `assert` raises `AssertionError` 224 | * Use `panic!()` before we implement exception 225 | 226 | ``` 227 | assert 1 == 1 228 | ``` 229 | ``` 230 | 1 0 LOAD_CONST 0 (1) 231 | 3 LOAD_CONST 0 (1) 232 | 6 COMPARE_OP 2 (==) 233 | 9 POP_JUMP_IF_TRUE 18 234 | 12 LOAD_GLOBAL 0 (AssertionError) 235 | 15 RAISE_VARARGS 1 236 | >> 18 LOAD_CONST 1 (None) 237 | 21 RETURN_VALUE 238 | ``` 239 | 240 | --- 241 | ### Native Function 242 | 243 | * e.g. `print()` 244 | 245 | ``` 246 | pub enum NativeType { 247 | NativeFunction(fn(Vec) -> NativeType), 248 | ... 249 | } 250 | 251 | match stack.pop() { 252 | NativeFunction(func) => return_val = func(), 253 | _ => ... 254 | } 255 | 256 | ``` 257 | 258 | --- 259 | 260 | ### Next steps 261 | * Exceptions 262 | * Make it run a small but popular tool/library 263 | * Implement the parser 264 | * Figure out garbage collection 265 | * Performance benchmarking 266 | 267 | --- 268 | ### Contribute 269 | 270 | ## https://github.com/shinglyu/RustPython 271 | 272 | ![qr_code](pic/repo_QR.png) 273 | 274 | --- 275 | class: middle, center 276 | 277 | # Thank you 278 | 279 | --- 280 | 281 | ### References 282 | * [`dis` documentation](https://docs.python.org/3.4/library/dis.html) 283 | * [byterun](http://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html) 284 | * [byterun (GitHub)](https://github.com/nedbat/byterun/) 285 | * [cpython source code](https://github.com/python/cpython) 286 | 287 | -------------------------------------------------------------------------------- /docs/slides/intro/pic/ice-cream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinglyu/RustPython/97c9855668382ab856d7091aaa4c9db8d8c3014b/docs/slides/intro/pic/ice-cream.jpg -------------------------------------------------------------------------------- /docs/slides/intro/slide.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | ##Python Interpreter in Rust 3 | ###Introduction 4 | #### 2017/3/28 5 | #### Shing Lyu 6 | 7 | 8 | ??? 9 | top, middle, bottom 10 | left, center, right 11 | 12 | --- 13 | name: toc 14 | ###Agenda 15 | 1. Category 16 | 1. Category 17 | 1. Category 18 | 1. Category 19 | 1. Category 20 | 1. Category 21 | 1. Category 22 | 23 | ??? 24 | This is a template 25 | --- 26 | 27 | ### Python's architecture 28 | * Interpreted 29 | * Garbage Collected 30 | * Compiler => bytecode => VM 31 | 32 | --- 33 | background-image: url('pic/ice-cream.jpg') 34 | class: bleed 35 | # Flavors 36 | 37 | 38 | --- 39 | ### Python Flavors 40 | * CPython (THE python) 41 | * Jython (JVM) 42 | * IronPython (.NET) 43 | * Pypy 44 | * Educational 45 | * Byterun 46 | * Jsapy (JS) 47 | * Brython (Python in browser) 48 | 49 | --- 50 | ### Why rewriting Python in Rust? 51 | * Memory safety 52 | 53 | * Learn about Python internal 54 | * Learn real world Rust 55 | 56 | --- 57 | ### Implementation strategy 58 | * Focus on the VM first, then the compiler 59 | * Reuse the Python built-in compiler to generate bytecode 60 | * Basic arithmetics 61 | * Control flows (require JUMP) 62 | * Function call (require call stack) 63 | * Built-in functions (require native code) 64 | * Run popular libraries 65 | 66 | 67 | --- 68 | ### References 69 | * [`dis` documentation](https://docs.python.org/3.4/library/dis.html) 70 | * [byterun](http://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html) 71 | * [byterun (GitHub)](https://github.com/nedbat/byterun/) 72 | * [cpython source code](https://github.com/python/cpython) 73 | 74 | --- 75 | ### How Python VM works 76 | * Stack machine 77 | * Accepts Python bytecode 78 | * `python -m dis source.py` 79 | 80 | --- 81 | 82 | ### A simple Python code 83 | 84 | ``` 85 | #!/usr/bin/env python3 86 | print(1+1) 87 | ``` 88 | 89 | We run `python3 -m dis source.py` 90 | 91 | --- 92 | 93 | ### The bytecode 94 | 95 | ``` 96 | 1 0 LOAD_NAME 0 (print) 97 | 3 LOAD_CONST 2 (2) 98 | 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 99 | 9 POP_TOP 100 | 10 LOAD_CONST 1 (None) 101 | 13 RETURN_VALUE 102 | 103 | ``` 104 | 105 | --- 106 | 107 | ### LOAD_NAME "print" 108 | * NAMES = ["print"] 109 | * CONSTS = [None, 2] 110 | * STACK: 111 | 112 | ``` 113 | | | 114 | | print (native code)| 115 | +--------------------+ 116 | ``` 117 | --- 118 | ### LOAD_CONST 2 119 | * NAMES = ["print"] 120 | * CONSTS = [None, 2] 121 | * STACK: 122 | 123 | ``` 124 | | | 125 | | 2 | 126 | | print (native code)| 127 | +--------------------+ 128 | ``` 129 | 130 | --- 131 | 132 | ### CALL_FUNCTION 1 133 | 1. argument = stack.pop() (argument == 2) 134 | 2. function = stack.top() (function == print) 135 | 3. call print(2) 136 | 137 | * NAMES = ["print"] 138 | * CONSTS = [None, 2] 139 | * STACK: 140 | 141 | ``` 142 | | | 143 | | print (native code)| 144 | +--------------------+ 145 | ``` 146 | --- 147 | ### POP_TOP 148 | * NAMES = ["print"] 149 | * CONSTS = [None, 2] 150 | * STACK: 151 | 152 | ``` 153 | | | 154 | | (empty) | 155 | +--------------------+ 156 | ``` 157 | 158 | --- 159 | ### LOAD_CONST 1 160 | * NAMES = ["print"] 161 | * CONSTS = [None, 2] 162 | * STACK: 163 | 164 | ``` 165 | | | 166 | | None | 167 | +--------------------+ 168 | ``` 169 | 170 | --- 171 | ### RETURN_VALUE 172 | 173 | (returns top of stack == None) 174 | 175 | --- 176 | 177 | ### Next step 178 | * Make it run a small but popular tool/library 179 | * Implement the parser 180 | * Performance benchmarking 181 | -------------------------------------------------------------------------------- /docs/study.md: -------------------------------------------------------------------------------- 1 | Topic to study 2 | ================== 3 | * How to save a Rust Iterator on the stack? 4 | * Study how blocks are handled in CPython. 5 | * The `why` var? 6 | * Why unwind everything when a `break` happened? 7 | -------------------------------------------------------------------------------- /init_env.sh: -------------------------------------------------------------------------------- 1 | virtualenv venv --python=python3 2 | source venv/bin/activate 3 | pip install bytecode 4 | 5 | source ~/.cargo/env 6 | rustup install nightly 7 | rustup default nightly 8 | -------------------------------------------------------------------------------- /python_compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "python_compiler" 3 | version = "0.1.0" 4 | authors = ["Shing Lyu "] 5 | 6 | [dependencies] 7 | cpython = { git = "https://github.com/dgrunwald/rust-cpython.git" } 8 | 9 | [dependencies.python27-sys] 10 | version = "*" 11 | -------------------------------------------------------------------------------- /python_compiler/examples/compile.rs: -------------------------------------------------------------------------------- 1 | extern crate python_compiler; 2 | 3 | use python_compiler::python_compiler::compile; 4 | 5 | fn main() { 6 | println!("{:?}", compile()); 7 | } 8 | -------------------------------------------------------------------------------- /python_compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cpython; 2 | extern crate python27_sys; 3 | 4 | pub mod python_compiler; 5 | -------------------------------------------------------------------------------- /python_compiler/src/python_compiler.rs: -------------------------------------------------------------------------------- 1 | use cpython::Python; 2 | use cpython::ObjectProtocol; //for call method 3 | use cpython::PyObject; 4 | use cpython::PyDict; 5 | use python27_sys::PyCodeObject; 6 | 7 | 8 | //pub fn compile() -> PyObject { 9 | pub fn compile(){ 10 | let gil = Python::acquire_gil(); 11 | let py = gil.python(); 12 | 13 | let locals = PyDict::new(py); 14 | // TODO: read the filename from commandline 15 | locals.set_item(py, "filename", "../tests/functions.py"); 16 | 17 | let load_file = "\ 18 | import os 19 | print(os.getcwd()) 20 | with open(filename, 'rU') as f:\ 21 | code = f.read()\ 22 | "; 23 | py.run(load_file, None, Some(&locals)).unwrap(); 24 | let code = py.eval("compile(code, \"foo\", \"exec\")", None, Some(&locals)).unwrap(); 25 | println!("{:?}", code.getattr(py, "co_name").unwrap()); 26 | println!("{:?}", code.getattr(py, "co_filename").unwrap()); 27 | println!("{:?}", code.getattr(py, "co_code").unwrap()); 28 | println!("{:?}", code.getattr(py, "co_freevars").unwrap()); 29 | println!("{:?}", code.getattr(py, "co_cellvars").unwrap()); 30 | println!("{:?}", code.getattr(py, "co_consts").unwrap()); 31 | let consts = code.getattr(py, "co_consts").unwrap(); 32 | println!("{:?}", consts.get_item(py, 0).unwrap().getattr(py, "co_code")); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | byteplay==0.2 2 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | print(2 + 3) 2 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: test.sh [--bytecode|--dis] 3 | # --bytecode: print the bytecode 4 | # --dis: run dis 5 | 6 | set -e 7 | 8 | MODE="run" 9 | 10 | case "${2}" in 11 | "--bytecode" ) 12 | MODE="view_bytecode" 13 | ;; 14 | "--dis" ) 15 | MODE="run_dis" 16 | ;; 17 | * ) 18 | ;; 19 | esac 20 | 21 | TESTCASE=$(basename ${1}) 22 | #TMP_FILE="test_${TESTCASE}.bytecode" 23 | TMP_FILE="${1}.bytecode" 24 | 25 | python compile_code.py "${1}" > "${TMP_FILE}" 26 | 27 | echo "${MODE}" 28 | case "${MODE}" in 29 | "run" ) 30 | cd RustPython 31 | RUST_BACKTRACE=1 cargo run "../${TMP_FILE}" 32 | ;; 33 | "view_bytecode" ) 34 | cat "${TMP_FILE}" | python -m json.tool 35 | ;; 36 | "run_dis" ) 37 | python -m dis "${1}" 38 | ;; 39 | * ) 40 | echo "Not a valid mode!" 41 | ;; 42 | esac 43 | 44 | -------------------------------------------------------------------------------- /test_all.bat: -------------------------------------------------------------------------------- 1 | :: win bat 2 | 3 | 4 | cd tests 5 | 6 | for %%i in (*.py) do python ../compile_code.py %%i >../bytes/%%i.bytecode 7 | cd .. 8 | 9 | REM cd RustPython 10 | REM for %%i in (../bytes/*.vbytecode) do echo %%i 11 | REM cd .. 12 | -------------------------------------------------------------------------------- /test_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # set -e 3 | 4 | source venv/bin/activate 5 | #TESTCASE=tests/variables.py 6 | # TESTCASE=tests/variables.py 7 | #TESTCASE=tests/minimum.py 8 | unexpected_count=0 9 | expected_count=0 10 | fail_titles=$"" 11 | 12 | RED='\033[0;31m' 13 | NC='\033[0m' # No Color 14 | 15 | for TESTCASE in $(find tests -name \*.py -print) 16 | do 17 | echo "TEST START: ${TESTCASE}" 18 | echo "--------------------------------" 19 | FILENAME="$(basename ${TESTCASE})" 20 | xfail=false 21 | if [ "${FILENAME:0:6}" = "xfail_" ]; then 22 | echo "Expected FAIL" 23 | xfail=true 24 | fi 25 | 26 | 27 | python compile_code.py $TESTCASE > $TESTCASE.bytecode 28 | cd RustPython 29 | cargo run ../$TESTCASE.bytecode 30 | 31 | 32 | if [[ $? -ne 0 ]]; then 33 | if [ "${xfail}" = true ]; then 34 | echo "== FAIL as expected ==" 35 | let expected_count=expected_count+1 36 | else 37 | printf "${RED}== FAIL, expected PASS ==${NC}\n" 38 | let unexpected_count=unexpected_count+1 39 | fail_titles=$"${fail_titles}\n${TESTCASE}\t${RED}FAIL${NC} (expected PASS)" 40 | fi 41 | else 42 | if [ "${xfail}" = true ]; then 43 | printf "${RED}== PASS, expected FAIL ==${NC}\n" 44 | let unexpected_count=unexpected_count+1 45 | let unexpected_count=unexpected_count+1 46 | fail_titles=$"${fail_titles}\n${TESTCASE}\t${RED}PASS${NC} (expected FAIL)" 47 | else 48 | echo "== PASS as expected ==" 49 | let expected_count=expected_count+1 50 | fi 51 | fi 52 | cd .. 53 | echo "--------------------------------" 54 | 55 | done 56 | 57 | echo "Summary" 58 | echo "================" 59 | printf "${RED}${unexpected_count} unexpected${NC}, ${expected_count} expected" 60 | echo "" 61 | echo "" 62 | echo "unexpected results:" 63 | printf "${fail_titles}" 64 | echo "" 65 | echo "================" 66 | -------------------------------------------------------------------------------- /tests/3.1.2.13.py: -------------------------------------------------------------------------------- 1 | word = "Python" 2 | 3 | assert "Python" == word[:2] + word[2:] 4 | assert "Python" == word[:4] + word[4:] 5 | 6 | assert "Py" == word[:2] 7 | assert "on" == word[4:] 8 | assert "on" == word[-2:] 9 | assert "Py" == word[:-4] 10 | # assert "Py" == word[::2] 11 | -------------------------------------------------------------------------------- /tests/3.1.2.16.py: -------------------------------------------------------------------------------- 1 | word = "Python" 2 | assert "on" == word[4:42] 3 | assert "" == word[42:] 4 | -------------------------------------------------------------------------------- /tests/3.1.2.18.py: -------------------------------------------------------------------------------- 1 | word = "Python" 2 | 3 | assert "Jython" == "J" + word[1:] 4 | assert "Pypy" == word[:2] + "py" 5 | -------------------------------------------------------------------------------- /tests/3.1.2.19.py: -------------------------------------------------------------------------------- 1 | s = "abcdefg" 2 | 3 | assert 7 == len(s) 4 | -------------------------------------------------------------------------------- /tests/3.1.3.2.py: -------------------------------------------------------------------------------- 1 | squares = [1, 4, 9, 16, 25] 2 | 3 | assert 1 == squares[0] 4 | assert 25 == squares[-1] 5 | assert [9, 16, 25] == squares[-3:] 6 | assert squares == squares[:] 7 | -------------------------------------------------------------------------------- /tests/3.1.3.4.py: -------------------------------------------------------------------------------- 1 | l = [1,2,3] 2 | assert [1,2,3,4,5] == (l + [4,5]) 3 | 4 | -------------------------------------------------------------------------------- /tests/basic_types.py: -------------------------------------------------------------------------------- 1 | # Spec: https://docs.python.org/2/library/types.html 2 | print(None) 3 | # TypeType 4 | # print(True) # LOAD_NAME??? 5 | print(1) 6 | # print(1L) # Long 7 | print(1.1) 8 | # ComplexType 9 | print("abc") 10 | print(u"abc") 11 | # Structural below 12 | # print((1, 2)) # Tuple can be any length, but fixed after declared 13 | # x = (1,2) 14 | # print(x[0]) # Tuple can be any length, but fixed after declared 15 | # print([1, 2, 3]) 16 | # print({"first":1,"second":2}) 17 | -------------------------------------------------------------------------------- /tests/builtin_len.py: -------------------------------------------------------------------------------- 1 | assert 3 == len([1,2,3]) 2 | assert 2 == len((1,2)) 3 | -------------------------------------------------------------------------------- /tests/cast.py: -------------------------------------------------------------------------------- 1 | x = 1 2 | y = 1.1 3 | assert x+y == 2.1 4 | #print(x+y) 5 | 6 | x = 1.1 7 | y = 1 8 | assert x+y == 2.1 9 | #print(x+y) 10 | 11 | x = 1.1 12 | y = 2.1 13 | assert x+y == 3.2 14 | #print(x+y) 15 | 16 | x = "ab" 17 | y = "cd" 18 | assert x+y == "abcd" 19 | #print(x+y) 20 | 21 | x = 2 22 | y = 3 23 | assert x**y == 8 24 | #print(x**y) 25 | 26 | x = 2.0 27 | y = 3 28 | assert x**y == 8.0 29 | #print(x**y) 30 | 31 | x = 2 32 | y = 3.0 33 | assert x**y == 8.0 34 | #print(x**y) 35 | 36 | x = 2.0 37 | y = 3.0 38 | assert x**y == 8.0 39 | #print(x**y) 40 | -------------------------------------------------------------------------------- /tests/floats.py: -------------------------------------------------------------------------------- 1 | 1 + 1.1 2 | -------------------------------------------------------------------------------- /tests/for.py: -------------------------------------------------------------------------------- 1 | x = 0 2 | for i in [1,2,3,4]: 3 | x += 1 4 | 5 | assert x == 4 6 | 7 | -------------------------------------------------------------------------------- /tests/function.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | return 42 3 | 4 | assert foo() == 42 5 | -------------------------------------------------------------------------------- /tests/function_args.py: -------------------------------------------------------------------------------- 1 | def sum(x, y): 2 | return x+y 3 | 4 | # def total(a, b, c, d): 5 | # return sum(sum(a,b), sum(c,d)) 6 | # 7 | # assert total(1,1,1,1) == 4 8 | # assert total(1,2,3,4) == 10 9 | 10 | assert sum(1,1) == 2 11 | assert sum(1,3) == 4 12 | 13 | def sum2y(x, y): 14 | return x+y*2 15 | 16 | assert sum2y(1,1) == 3 17 | assert sum2y(1,3) == 7 18 | -------------------------------------------------------------------------------- /tests/function_nested.py: -------------------------------------------------------------------------------- 1 | # Blocked on LOAD_GLOBAL 2 | def sum(x,y): 3 | return x+y 4 | 5 | def total(a,b,c,d): 6 | return sum(sum(a,b),sum(c,d)) 7 | 8 | assert total(1,2,3,4) == 10 9 | -------------------------------------------------------------------------------- /tests/if.py: -------------------------------------------------------------------------------- 1 | x = 0 2 | if 1 == 1: 3 | x += 5 4 | else: 5 | x += 3 6 | 7 | assert x == 5 8 | 9 | y = 0 10 | if 1 == 2: 11 | y += 5 12 | else: 13 | y += 3 14 | 15 | assert y == 3 16 | -------------------------------------------------------------------------------- /tests/intro/3.1.1.1.py: -------------------------------------------------------------------------------- 1 | assert 2 + 2 == 4 2 | 3 | assert 50 - 5*6 == 20 4 | 5 | assert (50 - 5*6) / 4 == 5 # This will crash 6 | assert (50 - 5*6) / 4 == 5.0 7 | 8 | assert 8 / 5 == 1.6 # division always returns a floating point number 9 | -------------------------------------------------------------------------------- /tests/intro/3.1.1.2.py: -------------------------------------------------------------------------------- 1 | assert 5.666666666666667 == 17 / 3 # classic division returns a float 2 | 3 | assert 5 == 17 // 3 # floor division discards the fractional part 4 | 5 | assert 2 == 17 % 3 # the % operator returns the remainder of the division 6 | 7 | assert 17 == 5 * 3 + 2 # result * divisor + remainder 8 | 9 | print(17 / 3) # classic division returns a float 10 | 11 | print(17 // 3) # floor division discards the fractional part 12 | 13 | print(17 % 3) # the % operator returns the remainder of the division 14 | 15 | print(5 * 3 + 2) # result * divisor + remainder 16 | -------------------------------------------------------------------------------- /tests/intro/3.1.1.3.py: -------------------------------------------------------------------------------- 1 | assert 25 == 5 ** 2 # 5 squared 2 | 3 | assert 128 == 2 ** 7 # 2 to the power of 7 4 | -------------------------------------------------------------------------------- /tests/intro/3.1.1.4.py: -------------------------------------------------------------------------------- 1 | width = 20 2 | height = 5 * 9 3 | assert 900 == width * height 4 | print(width * height) 5 | -------------------------------------------------------------------------------- /tests/intro/3.1.1.6.py: -------------------------------------------------------------------------------- 1 | assert 7.5 == 3 * 3.75 / 1.5 2 | 3 | assert 3.5 == 7.0 / 2 4 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.1.py: -------------------------------------------------------------------------------- 1 | assert 'spam eggs' == 'spam eggs' # single quotes 2 | assert "doesn't" == 'doesn\'t' # use \' to escape the single quote... 3 | assert "doesn't" == "doesn't" # ...or use double quotes instead 4 | assert '"Yes," he said.' == '"Yes," he said.' 5 | assert '"Yes," he said.' == "\"Yes,\" he said." 6 | assert '"Isn\'t," she said.' == '"Isn\'t," she said.' 7 | 8 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.10.py: -------------------------------------------------------------------------------- 1 | word = 'Python' 2 | assert 'P' == word[0] # character in position 0 3 | assert 'n' == word[5] # character in position 5 4 | assert 'n' == word[-1] # last character 5 | assert 'o' == word[-2] # second-last character 6 | assert 'P' == word[-6] 7 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.3.py: -------------------------------------------------------------------------------- 1 | print('C:\some\name') 2 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.4.py: -------------------------------------------------------------------------------- 1 | print("""\ 2 | Usage: thingy [OPTIONS] 3 | -h Display this usage message 4 | -H hostname Hostname to connect to 5 | """) 6 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.5.py: -------------------------------------------------------------------------------- 1 | assert 'unununium' == 3 * 'un' + 'ium' 2 | -------------------------------------------------------------------------------- /tests/intro/3.1.2.6.py: -------------------------------------------------------------------------------- 1 | assert 'Python' == 'Py' 'thon' 2 | -------------------------------------------------------------------------------- /tests/list.py: -------------------------------------------------------------------------------- 1 | x = [1,2,3] 2 | assert x[0] == 1 3 | assert x[1] == 2 4 | # assert x[7] 5 | -------------------------------------------------------------------------------- /tests/loop.py: -------------------------------------------------------------------------------- 1 | # i = 0 2 | # while i < 5: 3 | # i += 1 4 | # # print(i) 5 | # assert i == 5 6 | 7 | i = 0 8 | while i < 5: 9 | i += 1 10 | # print(i) 11 | if i == 3: 12 | break 13 | assert i == 3 14 | -------------------------------------------------------------------------------- /tests/math.py: -------------------------------------------------------------------------------- 1 | a = 4 2 | 3 | #print(a ** 3) 4 | #print(a * 3) 5 | #print(a / 2) 6 | #print(a % 3) 7 | #print(a - 3) 8 | #print(-a) 9 | #print(+a) 10 | 11 | assert a ** 3 == 64 12 | assert a * 3 == 12 13 | assert a / 2 == 2 14 | assert a % 3 == 1 15 | assert a - 3 == 1 16 | assert -a == -4 17 | assert +a == 4 18 | 19 | -------------------------------------------------------------------------------- /tests/minimum.py: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/print_const.py: -------------------------------------------------------------------------------- 1 | print(2 + 3) 2 | -------------------------------------------------------------------------------- /tests/tuple.py: -------------------------------------------------------------------------------- 1 | x = (1,2) 2 | assert x[0] == 1 3 | -------------------------------------------------------------------------------- /tests/types.py: -------------------------------------------------------------------------------- 1 | ['a', 1] 2 | -------------------------------------------------------------------------------- /tests/variables.py: -------------------------------------------------------------------------------- 1 | a = 8 2 | b = 7 3 | assert a+b == 15 4 | # print(a+b) 5 | -------------------------------------------------------------------------------- /tests/xfail_3.1.2.17.py: -------------------------------------------------------------------------------- 1 | word = "Python" 2 | 3 | word[0] = "J" # Should raise a error, immutable 4 | word[2:] = "Jy" # Should raise a error, immutable 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/xfail_assert.py: -------------------------------------------------------------------------------- 1 | assert 1 == 1 2 | assert 1 == 2 3 | --------------------------------------------------------------------------------