diff --git a/parser/src/lib.rs b/parser/src/lib.rs index f7b488bd9..2ace33281 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate log; -mod parser; +pub mod parser; mod python; pub mod ast; mod token; diff --git a/src/main.rs b/src/main.rs index 76949bb3d..7e32ee56d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,11 @@ mod compile; use clap::{Arg, App}; use std::path::Path; use rustpython_parser::parse; +use rustpython_parser::parser::parse_source; use rustpython_vm::evaluate; +use rustpython_vm::pyobject::PyObjectRef; +use std::io; +use std::io::prelude::*; fn main() { @@ -21,7 +25,7 @@ fn main() { .author(crate_authors!()) .about("Rust implementation of the Python language") .arg(Arg::with_name("script") - .required(true) + .required(false) .index(1)) .arg(Arg::with_name("v") .short("v") @@ -29,10 +33,16 @@ fn main() { .help("Give the verbosity")) .get_matches(); - // Figure out the filename: - let script_file = matches.value_of("script").unwrap_or("foo"); - debug!("Running file {}", script_file); + // Figure out if a script was passed: + match matches.value_of("script") { + None => run_shell(), + Some(filename) => run_script(&filename.to_string()), + } +} + +fn run_script(script_file: &String) { + debug!("Running file {}", script_file); // Parse an ast from it: let filepath = Path::new(script_file); match parse(filepath) { @@ -49,3 +59,31 @@ fn main() { } } +fn run_shell() { + println!("Welcome to the magnificent Rust Python interpreter"); + // Read a single line: + loop { + let mut input = String::new(); + print!(">>>"); + io::stdout().flush().ok().expect("Could not flush stdout"); + io::stdin().read_line(&mut input); + println!("You entered {:?}", input); + let result = eval(&input); + println!("{:?}", result); + } +} + +fn eval(source: &String) -> Result { + match parse_source(source) { + Ok(program) => { + debug!("Got ast: {:?}", program); + let bytecode = compile::compile(program); + debug!("Code object: {:?}", bytecode); + evaluate(bytecode) + }, + Err(msg) => { + panic!("Parsing went horribly wrong: {}", msg); + }, + } +} + diff --git a/py_code_object/compile_code.py b/tests/compile_code.py similarity index 100% rename from py_code_object/compile_code.py rename to tests/compile_code.py diff --git a/tests/3.1.2.13.py b/tests/snippets/3.1.2.13.py similarity index 100% rename from tests/3.1.2.13.py rename to tests/snippets/3.1.2.13.py diff --git a/tests/3.1.2.16.py b/tests/snippets/3.1.2.16.py similarity index 100% rename from tests/3.1.2.16.py rename to tests/snippets/3.1.2.16.py diff --git a/tests/3.1.2.18.py b/tests/snippets/3.1.2.18.py similarity index 100% rename from tests/3.1.2.18.py rename to tests/snippets/3.1.2.18.py diff --git a/tests/3.1.2.19.py b/tests/snippets/3.1.2.19.py similarity index 100% rename from tests/3.1.2.19.py rename to tests/snippets/3.1.2.19.py diff --git a/tests/3.1.3.2.py b/tests/snippets/3.1.3.2.py similarity index 100% rename from tests/3.1.3.2.py rename to tests/snippets/3.1.3.2.py diff --git a/tests/3.1.3.4.py b/tests/snippets/3.1.3.4.py similarity index 100% rename from tests/3.1.3.4.py rename to tests/snippets/3.1.3.4.py diff --git a/tests/3.1.3.5.py b/tests/snippets/3.1.3.5.py similarity index 100% rename from tests/3.1.3.5.py rename to tests/snippets/3.1.3.5.py diff --git a/tests/append.py b/tests/snippets/append.py similarity index 100% rename from tests/append.py rename to tests/snippets/append.py diff --git a/tests/basic_types.py b/tests/snippets/basic_types.py similarity index 100% rename from tests/basic_types.py rename to tests/snippets/basic_types.py diff --git a/tests/builtin_len.py b/tests/snippets/builtin_len.py similarity index 100% rename from tests/builtin_len.py rename to tests/snippets/builtin_len.py diff --git a/tests/cast.py b/tests/snippets/cast.py similarity index 100% rename from tests/cast.py rename to tests/snippets/cast.py diff --git a/tests/floats.py b/tests/snippets/floats.py similarity index 100% rename from tests/floats.py rename to tests/snippets/floats.py diff --git a/tests/for.py b/tests/snippets/for.py similarity index 100% rename from tests/for.py rename to tests/snippets/for.py diff --git a/tests/function.py b/tests/snippets/function.py similarity index 100% rename from tests/function.py rename to tests/snippets/function.py diff --git a/tests/function_args.py b/tests/snippets/function_args.py similarity index 100% rename from tests/function_args.py rename to tests/snippets/function_args.py diff --git a/tests/function_nested.py b/tests/snippets/function_nested.py similarity index 100% rename from tests/function_nested.py rename to tests/snippets/function_nested.py diff --git a/tests/if.py b/tests/snippets/if.py similarity index 100% rename from tests/if.py rename to tests/snippets/if.py diff --git a/tests/intro/3.1.1.1.py b/tests/snippets/intro/3.1.1.1.py similarity index 100% rename from tests/intro/3.1.1.1.py rename to tests/snippets/intro/3.1.1.1.py diff --git a/tests/intro/3.1.1.2.py b/tests/snippets/intro/3.1.1.2.py similarity index 100% rename from tests/intro/3.1.1.2.py rename to tests/snippets/intro/3.1.1.2.py diff --git a/tests/intro/3.1.1.3.py b/tests/snippets/intro/3.1.1.3.py similarity index 100% rename from tests/intro/3.1.1.3.py rename to tests/snippets/intro/3.1.1.3.py diff --git a/tests/intro/3.1.1.4.py b/tests/snippets/intro/3.1.1.4.py similarity index 100% rename from tests/intro/3.1.1.4.py rename to tests/snippets/intro/3.1.1.4.py diff --git a/tests/intro/3.1.1.6.py b/tests/snippets/intro/3.1.1.6.py similarity index 100% rename from tests/intro/3.1.1.6.py rename to tests/snippets/intro/3.1.1.6.py diff --git a/tests/intro/3.1.2.1.py b/tests/snippets/intro/3.1.2.1.py similarity index 100% rename from tests/intro/3.1.2.1.py rename to tests/snippets/intro/3.1.2.1.py diff --git a/tests/intro/3.1.2.10.py b/tests/snippets/intro/3.1.2.10.py similarity index 100% rename from tests/intro/3.1.2.10.py rename to tests/snippets/intro/3.1.2.10.py diff --git a/tests/intro/3.1.2.3.py b/tests/snippets/intro/3.1.2.3.py similarity index 100% rename from tests/intro/3.1.2.3.py rename to tests/snippets/intro/3.1.2.3.py diff --git a/tests/intro/3.1.2.4.py b/tests/snippets/intro/3.1.2.4.py similarity index 100% rename from tests/intro/3.1.2.4.py rename to tests/snippets/intro/3.1.2.4.py diff --git a/tests/intro/3.1.2.5.py b/tests/snippets/intro/3.1.2.5.py similarity index 100% rename from tests/intro/3.1.2.5.py rename to tests/snippets/intro/3.1.2.5.py diff --git a/tests/intro/3.1.2.6.py b/tests/snippets/intro/3.1.2.6.py similarity index 100% rename from tests/intro/3.1.2.6.py rename to tests/snippets/intro/3.1.2.6.py diff --git a/tests/list.py b/tests/snippets/list.py similarity index 100% rename from tests/list.py rename to tests/snippets/list.py diff --git a/tests/loop.py b/tests/snippets/loop.py similarity index 100% rename from tests/loop.py rename to tests/snippets/loop.py diff --git a/tests/math.py b/tests/snippets/math.py similarity index 100% rename from tests/math.py rename to tests/snippets/math.py diff --git a/tests/minimum.py b/tests/snippets/minimum.py similarity index 100% rename from tests/minimum.py rename to tests/snippets/minimum.py diff --git a/tests/perf_add.py b/tests/snippets/perf_add.py similarity index 100% rename from tests/perf_add.py rename to tests/snippets/perf_add.py diff --git a/tests/perf_fib.py b/tests/snippets/perf_fib.py similarity index 100% rename from tests/perf_fib.py rename to tests/snippets/perf_fib.py diff --git a/tests/print_const.py b/tests/snippets/print_const.py similarity index 100% rename from tests/print_const.py rename to tests/snippets/print_const.py diff --git a/tests/tuple.py b/tests/snippets/tuple.py similarity index 100% rename from tests/tuple.py rename to tests/snippets/tuple.py diff --git a/tests/types.py b/tests/snippets/types.py similarity index 100% rename from tests/types.py rename to tests/snippets/types.py diff --git a/tests/variables.py b/tests/snippets/variables.py similarity index 100% rename from tests/variables.py rename to tests/snippets/variables.py diff --git a/tests/xfail_3.1.2.17.py b/tests/snippets/xfail_3.1.2.17.py similarity index 100% rename from tests/xfail_3.1.2.17.py rename to tests/snippets/xfail_3.1.2.17.py diff --git a/tests/xfail_assert.py b/tests/snippets/xfail_assert.py similarity index 100% rename from tests/xfail_assert.py rename to tests/snippets/xfail_assert.py diff --git a/py_code_object/test_snippets.py b/tests/test_snippets.py similarity index 87% rename from py_code_object/test_snippets.py rename to tests/test_snippets.py index 382d765a2..7acb8d66a 100644 --- a/py_code_object/test_snippets.py +++ b/tests/test_snippets.py @@ -3,6 +3,7 @@ # in the tests folder. +import sys import os import unittest import glob @@ -15,7 +16,7 @@ import compile_code logger = logging.getLogger('tests') ROOT_DIR = '..' -TEST_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'tests')) +TEST_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'tests', 'snippets')) CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'py_code_object')) RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR)) @@ -32,6 +33,8 @@ def perform_test(filename, method): logger.info('Running %s via %s', filename, method) if method == 'cpython': run_via_cpython(filename) + elif method == 'cpython_bytecode': + run_via_cpython_bytecode(filename) elif method == 'rustpython': run_via_rustpython(filename) else: @@ -39,6 +42,11 @@ def perform_test(filename, method): def run_via_cpython(filename): + """ Simply invoke python itself on the script """ + subprocess.check_call([sys.executable, filename]) + + +def run_via_cpython_bytecode(filename): # Step1: Create bytecode file: bytecode_filename = filename + '.bytecode' with open(bytecode_filename, 'w') as f: @@ -77,7 +85,7 @@ def create_test_function(cls, filename, method): def populate(cls): """ Decorator function which can populate a unittest.TestCase class """ - for method in ['cpython', 'rustpython']: + for method in ['cpython', 'cpython_bytecode', 'rustpython']: for filename in get_test_files(): create_test_function(cls, filename, method) return cls diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 82e82a458..46f7f9d01 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -5,7 +5,7 @@ extern crate env_logger; //extern crate eval; use eval::eval::*; // use py_code_object::{Function, NativeType, PyCodeObject}; -mod pyobject; +pub mod pyobject; pub mod bytecode; mod builtins; mod vm; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c2e29831c..8797269cb 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -47,6 +47,9 @@ pub enum PyObject { stop: Option, step: Option, }, + NameError { // TODO: improve python object and type system + name: String, + }, Code { code: bytecode::CodeObject, }, diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3f4f20067..4cbc8d51a 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -15,14 +15,14 @@ use super::pyobject::{PyObject, PyObjectRef}; // use objects::objects; // Container of the virtual machine state: -pub fn evaluate(code: bytecode::CodeObject) { +pub fn evaluate(code: bytecode::CodeObject) -> Result { let mut vm = VirtualMachine::new(); // Register built in function: // vm.scope.insert(String::from("print"), PyObject::RustFunction { function: builtins::print }.into_ref()); // { stack: Vec::new() }; - vm.run(Rc::new(code)); + vm.run(Rc::new(code)) } // Objects are live when they are on stack, or referenced by a name (for now) @@ -51,11 +51,6 @@ struct VirtualMachine { frames: Vec, } -enum ExeResult { - Value { value: PyObjectRef }, - Exception, -} - impl Frame { pub fn new(code: Rc, callargs: HashMap, globals: Option>) -> Frame { //populate the globals and locals @@ -122,22 +117,25 @@ impl VirtualMachine { self.current_frame().pop_multiple(count) } - fn run(&mut self, code: Rc) { + fn run(&mut self, code: Rc) -> Result { let frame = Frame::new(code, HashMap::new(), None); - self.run_frame(frame); + self.run_frame(frame) } // The Option is the return value of the frame, remove when we have implemented frame // TODO: read the op codes directly from the internal code object - fn run_frame(&mut self, mut frame: Frame) -> PyObjectRef { + fn run_frame(&mut self, mut frame: Frame) -> Result { self.frames.push(frame); let value = loop { let result = self.execute_instruction(); match result { None => {}, - Some(ExeResult::Value { value }) => { break value; }, - Some(ExeResult::Exception) => { panic!("TODO"); } + Some(Ok(value)) => { break Ok(value); }, + Some(Err(value)) => { + // TODO: unwind stack on exception. + panic!("TODO {:?}", value); + } } /*if curr_frame.blocks.len() > 0 { self.manage_block_stack(&why); @@ -238,7 +236,7 @@ impl VirtualMachine { } // Execute a single instruction: - fn execute_instruction(&mut self) -> Option { + fn execute_instruction(&mut self) -> Option> { // let frame = self.frames.last_mut().unwrap(); let instruction = { let frame = self.current_frame(); @@ -268,9 +266,14 @@ impl VirtualMachine { }, &bytecode::Instruction::LoadName { ref name } => { // Lookup name in scope and put it onto the stack! - let obj = self.current_frame().locals[name].clone(); - self.push_value(obj); - None + if self.current_frame().locals.contains_key(name) { + let obj = self.current_frame().locals[name].clone(); + self.push_value(obj); + None + } else { + let name_error = PyObject::NameError { name: name.to_string() }; + Some(Err(name_error.into_ref())) + } }, &bytecode::Instruction::StoreName { ref name } => { // take top of stack and assign in scope: @@ -342,7 +345,7 @@ impl VirtualMachine { }, &bytecode::Instruction::ReturnValue => { let value = self.pop_value(); - Some(ExeResult::Value { value }) + Some(Ok(value)) }, /* &bytecode::Instruction::PushBlock { start, end } => { @@ -401,9 +404,9 @@ impl VirtualMachine { let mut args: Vec = self.pop_multiple(count); let func_ref = self.pop_value(); let f = func_ref.borrow();// = &*func_ref.borrow(); - let result = match *f { + let func_result = match *f { PyObject::RustFunction { ref function } => { - f.call(args) + Ok(f.call(args)) } PyObject::Function { ref code } => { let frame = Frame::new(Rc::new(code.clone()), HashMap::new(), None); @@ -414,11 +417,17 @@ impl VirtualMachine { panic!("Not impl"); } }; - self.push_value(result); + + match func_result { + Ok(value) => { self.push_value(value); None }, + Err(value) => { + // Ripple exception upwards: + Some(Err(value)) + } + } // call_stack.push(); // If a builtin function, then call directly, otherwise, execute it? // execute(function.code); - None }, /* TODO &bytecode::Instruction::Jump { target } => {