Merge pull request #100 from OddBloke/kwargs

Add support for function argument defaults
This commit is contained in:
Windel Bouwman
2018-08-29 15:20:57 +02:00
committed by GitHub
11 changed files with 222 additions and 38 deletions

9
Cargo.lock generated
View File

@@ -62,7 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -91,7 +91,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -160,7 +160,7 @@ name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -466,6 +466,7 @@ dependencies = [
name = "rustpython_vm"
version = "0.1.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustpython_parser 0.0.1",
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -703,7 +704,7 @@ dependencies = [
"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"

View File

@@ -95,12 +95,12 @@ pub enum Statement {
ClassDef {
name: String,
body: Vec<LocatedStatement>,
args: Vec<String>,
args: Vec<(String, Option<Expression>)>,
// TODO: docstring: String,
},
FunctionDef {
name: String,
args: Vec<String>,
args: Vec<(String, Option<Expression>)>,
// docstring: String,
body: Vec<LocatedStatement>,
},
@@ -161,7 +161,7 @@ pub enum Expression {
name: String,
},
Lambda {
args: Vec<String>,
args: Vec<(String, Option<Expression>)>,
body: Box<Expression>,
},
True,

View File

@@ -193,7 +193,7 @@ mod tests {
location: ast::Location::new(1, 1),
node: ast::Statement::Expression {
expression: ast::Expression::Lambda {
args: vec![String::from("x"), String::from("y")],
args: vec![(String::from("x"), None), (String::from("y"), None)],
body: Box::new(ast::Expression::Binop {
a: Box::new(ast::Expression::Identifier {
name: String::from("x"),
@@ -211,25 +211,46 @@ mod tests {
#[test]
fn test_parse_class() {
let source = String::from("class Foo(A, B):\n def __init__(self):\n pass\n");
let source = String::from("class Foo(A, B):\n def __init__(self):\n pass\n def method_with_default(self, arg='default'):\n pass\n");
assert_eq!(
parse_statement(&source),
Ok(ast::LocatedStatement {
location: ast::Location::new(1, 1),
node: ast::Statement::ClassDef {
name: String::from("Foo"),
args: vec![String::from("A"), String::from("B")],
body: vec![ast::LocatedStatement {
location: ast::Location::new(2, 2),
node: ast::Statement::FunctionDef {
name: String::from("__init__"),
args: vec![String::from("self")],
body: vec![ast::LocatedStatement {
location: ast::Location::new(3, 3),
node: ast::Statement::Pass,
}],
args: vec![(String::from("A"), None), (String::from("B"), None)],
body: vec![
ast::LocatedStatement {
location: ast::Location::new(2, 2),
node: ast::Statement::FunctionDef {
name: String::from("__init__"),
args: vec![(String::from("self"), None)],
body: vec![ast::LocatedStatement {
location: ast::Location::new(3, 3),
node: ast::Statement::Pass,
}],
}
},
ast::LocatedStatement {
location: ast::Location::new(4, 2),
node: ast::Statement::FunctionDef {
name: String::from("method_with_default"),
args: vec![
(String::from("self"), None),
(
String::from("arg"),
Some(ast::Expression::String {
value: "default".to_string()
})
)
],
body: vec![ast::LocatedStatement {
location: ast::Location::new(5, 3),
node: ast::Statement::Pass,
}],
}
}
}],
],
}
})
)

View File

@@ -308,12 +308,19 @@ FuncDef: ast::LocatedStatement = {
},
};
Parameters: Vec<String> = {
Parameters: Vec<(String, Option<ast::Expression>)> = {
"(" <a: TypedArgsList> ")" => a,
};
TypedArgsList: Vec<String> = {
<a: Comma<Identifier>> => a,
// parameters are (String, None), kwargs are (String, Some(Test)) where Test is
// the default
TypedArgsList: Vec<(String, Option<ast::Expression>)> = {
<a: Comma<Parameter>> => a
};
Parameter: (String, Option<ast::Expression>) = {
<i:Identifier> => (i.clone(), None),
<i:Identifier> "=" <e:Expression> => (i.clone(), Some(e.clone())),
};
ClassDef: ast::LocatedStatement = {

View File

@@ -0,0 +1,66 @@
def no_args():
pass
no_args()
try:
no_args('one_arg')
except TypeError:
pass
else:
assert False, 'no TypeError raised: 1 arg to no_args'
def one_arg(arg):
pass
one_arg('one_arg')
try:
one_arg()
except TypeError:
pass
else:
assert False, 'no TypeError raised: no args to one_arg'
try:
one_arg('one_arg', 'two_arg')
except TypeError:
pass
else:
assert False, 'no TypeError raised: two args to one_arg'
def one_default_arg(arg="default"):
return arg
assert 'default' == one_default_arg()
assert 'arg' == one_default_arg('arg')
try:
one_default_arg('one_arg', 'two_arg')
except TypeError:
pass
else:
assert False, 'no TypeError raised: two args to one_default_arg'
def one_normal_one_default_arg(pos, arg="default"):
return pos, arg
assert ('arg', 'default') == one_normal_one_default_arg('arg')
assert ('arg', 'arg2') == one_normal_one_default_arg('arg', 'arg2')
try:
one_normal_one_default_arg()
except TypeError:
pass
else:
assert False, 'no TypeError raised: two args to one_default_arg'
try:
one_normal_one_default_arg('one', 'two', 'three')
except TypeError:
pass
else:
assert False, 'no TypeError raised: two args to one_default_arg'

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Shing Lyu <shing.lyu@gmail.com>"]
[dependencies]
bitflags = "1.0.4"
log = "0.3"
rustpython_parser = {path = "../parser"}
serde = "1.0.66"

View File

@@ -37,6 +37,12 @@ impl CodeObject {
}
}
bitflags! {
pub struct FunctionOpArg: u8 {
const HAS_DEFAULTS = 0x01;
}
}
pub type Label = usize;
#[derive(Debug, Clone, PartialEq)]
@@ -88,7 +94,9 @@ pub enum Instruction {
JumpIfFalse {
target: Label,
},
MakeFunction,
MakeFunction {
flags: FunctionOpArg,
},
CallFunction {
count: usize,
},

View File

@@ -350,8 +350,28 @@ impl Compiler {
}
ast::Statement::FunctionDef { name, args, body } => {
// Create bytecode for this function:
self.code_object_stack
.push(CodeObject::new(args.to_vec(), None));
let mut names = vec![];
let mut default_elements = vec![];
for (name, default) in args {
names.push(name.clone());
if let Some(default) = default {
default_elements.push(default.clone());
} else {
if default_elements.len() > 0 {
// Once we have started with defaults, all remaining arguments must
// have defaults
panic!("non-default argument follows default argument: {}", name);
}
}
}
let have_kwargs = default_elements.len() > 0;
self.compile_expression(&ast::Expression::Tuple {
elements: default_elements,
});
self.code_object_stack.push(CodeObject::new(names, None));
self.compile_statements(body);
// Emit None at end:
@@ -371,7 +391,11 @@ impl Compiler {
});
// Turn code object into function object:
self.emit(Instruction::MakeFunction);
let mut flags = bytecode::FunctionOpArg::empty();
if have_kwargs {
flags = flags | bytecode::FunctionOpArg::HAS_DEFAULTS;
}
self.emit(Instruction::MakeFunction { flags: flags });
self.emit(Instruction::StoreName {
name: name.to_string(),
});
@@ -400,7 +424,9 @@ impl Compiler {
},
});
// Turn code object into function object:
self.emit(Instruction::MakeFunction);
self.emit(Instruction::MakeFunction {
flags: bytecode::FunctionOpArg::empty(),
});
self.emit(Instruction::LoadConst {
value: bytecode::Constant::String {
@@ -409,7 +435,9 @@ impl Compiler {
});
for base in args {
self.emit(Instruction::LoadName { name: base.clone() });
self.emit(Instruction::LoadName {
name: base.0.clone(),
});
}
self.emit(Instruction::CallFunction {
count: 2 + args.len(),
@@ -720,8 +748,10 @@ impl Compiler {
});
}
ast::Expression::Lambda { args, body } => {
self.code_object_stack
.push(CodeObject::new(args.to_vec(), None));
self.code_object_stack.push(CodeObject::new(
args.iter().map(|(name, _default)| name.clone()).collect(),
None,
));
self.compile_expression(body);
self.emit(Instruction::ReturnValue);
let code = self.code_object_stack.pop().unwrap();
@@ -734,7 +764,9 @@ impl Compiler {
},
});
// Turn code object into function object:
self.emit(Instruction::MakeFunction);
self.emit(Instruction::MakeFunction {
flags: bytecode::FunctionOpArg::empty(),
});
}
}
}

View File

@@ -1,4 +1,6 @@
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate log;
// extern crate env_logger;
extern crate serde;

View File

@@ -267,11 +267,17 @@ impl PyContext {
)
}
pub fn new_function(&self, code_obj: PyObjectRef, scope: PyObjectRef) -> PyObjectRef {
pub fn new_function(
&self,
code_obj: PyObjectRef,
scope: PyObjectRef,
defaults: PyObjectRef,
) -> PyObjectRef {
PyObject::new(
PyObjectKind::Function {
code: code_obj,
scope: scope,
defaults: defaults,
},
self.function_type(),
)
@@ -566,6 +572,7 @@ pub enum PyObjectKind {
Function {
code: PyObjectRef,
scope: PyObjectRef,
defaults: PyObjectRef,
},
BoundMethod {
function: PyObjectRef,
@@ -613,7 +620,7 @@ impl fmt::Debug for PyObjectKind {
} => write!(f, "slice"),
&PyObjectKind::NameError { name: _ } => write!(f, "NameError"),
&PyObjectKind::Code { ref code } => write!(f, "code: {:?}", code),
&PyObjectKind::Function { code: _, scope: _ } => write!(f, "function"),
&PyObjectKind::Function { .. } => write!(f, "function"),
&PyObjectKind::BoundMethod {
ref function,
ref object,
@@ -683,7 +690,7 @@ impl PyObject {
} => format!("<class '{}'>", name),
PyObjectKind::Instance { dict: _ } => format!("<instance>"),
PyObjectKind::Code { code: _ } => format!("<code>"),
PyObjectKind::Function { code: _, scope: _ } => format!("<func>"),
PyObjectKind::Function { .. } => format!("<func>"),
PyObjectKind::BoundMethod { .. } => format!("<bound-method>"),
PyObjectKind::RustFunction { function: _ } => format!("<rustfunc>"),
PyObjectKind::Module { ref name, dict: _ } => format!("<module '{}'>", name),

View File

@@ -516,10 +516,44 @@ impl VirtualMachine {
PyObjectKind::Function {
ref code,
ref scope,
ref defaults,
} => {
let mut scope = self.ctx.new_scope(Some(scope.clone()));
let code_object = copy_code(code.clone());
for (name, value) in code_object.arg_names.iter().zip(args.args) {
let nargs = args.args.len();
let nexpected_args = code_object.arg_names.len();
let args = if nargs > nexpected_args {
return Err(self.new_type_error(format!(
"Expected {} arguments (got: {})",
nexpected_args, nargs
)));
} else if nargs < nexpected_args {
// Use defaults if available
let nrequired_defaults = nexpected_args - nargs;
let available_defaults = match defaults.borrow().kind {
PyObjectKind::Tuple { ref elements } => elements.clone(),
PyObjectKind::None => vec![],
_ => panic!("function defaults not tuple or None"),
};
if nrequired_defaults > available_defaults.len() {
return Err(self.new_type_error(format!(
"Expected {}-{} arguments (got: {})",
nexpected_args - available_defaults.len(),
nexpected_args,
nargs
)));
}
let default_args = available_defaults
.clone()
.split_off(available_defaults.len() - nrequired_defaults);
let mut new_args = args.args.clone();
new_args.extend(default_args);
new_args
} else {
// nargs == nexpected_args
args.args
};
for (name, value) in code_object.arg_names.iter().zip(args) {
scope.set_item(name, value);
}
let frame = Frame::new(code.clone(), scope);
@@ -771,13 +805,18 @@ impl VirtualMachine {
};
None
}
bytecode::Instruction::MakeFunction => {
bytecode::Instruction::MakeFunction { flags } => {
let _qualified_name = self.pop_value();
let code_obj = self.pop_value();
let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) {
self.pop_value()
} else {
self.get_none()
};
// pop argc arguments
// argument: name, args, globals
let scope = self.current_frame().locals.clone();
let obj = self.ctx.new_function(code_obj, scope);
let obj = self.ctx.new_function(code_obj, scope, defaults);
self.push_value(obj);
None
}