Skip to content

Commit

Permalink
Treewalk: Support complex(), remove ModuleInterface in favor of Membe…
Browse files Browse the repository at this point in the history
…rAccess, move cpython mod under treewalk
  • Loading branch information
Jones Beach committed Jun 2, 2024
1 parent a4405ef commit f4abe6e
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 80 deletions.
2 changes: 1 addition & 1 deletion docs/SUPPORTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
|`chr`||
|`classmethod`||
|`compile`||
|`complex`||
|`complex`||
|`delattr`||
|`dict`||
|`dir`||
Expand Down
1 change: 1 addition & 0 deletions examples/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
print(bool)
print(bytearray)
print(classmethod)
print(complex)
print(dict)
print(frozenset)
print(getattr)
Expand Down
5 changes: 5 additions & 0 deletions examples/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,8 @@ def test_kwargs(**kwargs):
print(slice(2))
print(slice(2,3))
print(slice(2,3,4))

print(complex())
print(complex(0, 1))
print(complex(1, 1))
print(complex(1.1, 1.1))
86 changes: 65 additions & 21 deletions src/treewalk/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::parser::Parser;
use crate::treewalk::types::{
function::FunctionType,
iterators::GeneratorIterator,
traits::{Callable, MemberAccessor, ModuleInterface},
traits::{Callable, MemberAccessor},
utils::{Dunder, ResolvedArguments},
Bytes, Class, Coroutine, Dict, ExprResult, Function, Generator, List, Module, Set, Slice, Str,
Tuple,
Expand Down Expand Up @@ -193,24 +193,6 @@ impl Interpreter {
}
}

/// Let's walk through all of the cases that are supported here.
///
/// expression | initiated by | found on | callable | returns | supported
/// -------------- ---------------------------------------------------------------------------------------------
/// foo.attr | instance | instance | no | 4 | yes
/// foo.func | instance | class | yes | <bound method Foo.func of {object}> | yes
/// Foo.attr | class | <none> | N/A | <class 'AttributeError'> | yes
/// Foo.func | class | class | yes | <function Foo.func at {addr}> | yes
/// foo.class_attr | instance | class | no | 6 | yes
/// Foo.class_attr | class | class | no | 6 | yes
/// [].__doc__ | instance | class | no | {doc_string} | later
/// [].append | instance | class | yes | <built-in method append of list {obj}> | yes
/// list.__doc__ | class | class | no | {doc_string} | later
/// list.append | class | class | yes | <method 'append' of 'list' objects> | yes
/// list.__dict__ | class | metaclass | no | {mappingproxy} | yes
/// list.mro | class | metaclass | yes | <built-in method mro of type {obj}> | later
/// type.__dict__ | metaclass | metaclass | no | {mappingproxy} | yes
/// type.mro | metaclass | metaclass | yes | <method 'mro' of 'type' objects> | later
fn evaluate_member_access(
&self,
object: &Expr,
Expand Down Expand Up @@ -1114,7 +1096,7 @@ impl Interpreter {
}
};

if let Some(value) = ModuleInterface::get(&*module, self, module_symbol.as_str()) {
if let Some(value) = module.get_member(self, module_symbol.as_str())? {
self.state.write(&aliased_symbol, value.clone());
} else {
return Err(InterpreterError::FunctionNotFound(
Expand Down Expand Up @@ -1459,7 +1441,9 @@ mod tests {

use super::*;
use crate::core::Storable;
use crate::treewalk::types::{ByteArray, DictItems, DictKeys, DictValues, FrozenSet, Type};
use crate::treewalk::types::{
ByteArray, Complex, DictItems, DictKeys, DictValues, FrozenSet, Type,
};
use crate::types::errors::ParserError;

fn downcast<T: InterpreterEntrypoint + 'static>(input: T) -> Interpreter {
Expand Down Expand Up @@ -5435,6 +5419,9 @@ f = type(sys)
g = type(itertools)
h = type(_thread)
i = type(sys.builtin_module_names)
j = type(posix)
"#;
let (mut parser, mut interpreter) = init(input);

Expand Down Expand Up @@ -5501,6 +5488,14 @@ h = type(_thread)
interpreter.state.read("h").unwrap().as_class().unwrap(),
interpreter.state.get_type_class(Type::Module)
);
assert_eq!(
interpreter.state.read("i").unwrap().as_class().unwrap(),
interpreter.state.get_type_class(Type::Tuple)
);
assert_eq!(
interpreter.state.read("j").unwrap().as_class().unwrap(),
interpreter.state.get_type_class(Type::Module)
);
}
}
}
Expand Down Expand Up @@ -8688,6 +8683,55 @@ a = obj.attribute
}
}
}

#[test]
fn complex() {
let input = r#"
a = complex(4, 5)
b = type(a)
c = complex()
d = complex(1)
e = complex("2+3j")
f = complex("2.1+3.1j")
g = complex(4.1, 5.1)
"#;

let (mut parser, mut interpreter) = init(input);

match interpreter.run(&mut parser) {
Err(e) => panic!("Interpreter error: {:?}", e),
Ok(_) => {
assert_eq!(
interpreter.state.read("a"),
Some(ExprResult::Complex(Complex::new(4.0, 5.0)))
);
assert_eq!(
interpreter.state.read("b").unwrap().as_class().unwrap(),
interpreter.state.get_type_class(Type::Complex)
);
assert_eq!(
interpreter.state.read("c"),
Some(ExprResult::Complex(Complex::new(0.0, 0.0)))
);
assert_eq!(
interpreter.state.read("d"),
Some(ExprResult::Complex(Complex::new(1.0, 0.0)))
);
assert_eq!(
interpreter.state.read("e"),
Some(ExprResult::Complex(Complex::new(2.0, 3.0)))
);
assert_eq!(
interpreter.state.read("f"),
Some(ExprResult::Complex(Complex::new(2.1, 3.1)))
);
assert_eq!(
interpreter.state.read("g"),
Some(ExprResult::Complex(Complex::new(4.1, 5.1)))
);
}
}
}
//
// #[cfg(feature = "c_stdlib")]
// #[test]
Expand Down
4 changes: 2 additions & 2 deletions src/treewalk/scope_manager.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::core::{Container, Stack};
use crate::domain::Context;
use crate::treewalk::executor::{AsyncioCreateTaskBuiltin, AsyncioRunBuiltin, AsyncioSleepBuiltin};
#[cfg(feature = "c_stdlib")]
use crate::treewalk::types::cpython::utils as cpython_utils;
use crate::treewalk::types::{
builtins::{
GetattrBuiltin, GlobalsBuiltin, IsinstanceBuiltin, IssubclassBuiltin, IterBuiltin,
Expand All @@ -10,8 +12,6 @@ use crate::treewalk::types::{
utils::EnvironmentFrame,
ExprResult, Module,
};
#[cfg(feature = "c_stdlib")]
use crate::types::cpython::utils as cpython_utils;

use super::{LoadedModule, Scope, TypeRegistry};

Expand Down
10 changes: 6 additions & 4 deletions src/treewalk/type_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use crate::core::Container;
use crate::treewalk::types::{
iterators::{ReversedIterator, ZipIterator},
traits::{Callable, MemberAccessor, NonDataDescriptor},
Bool, ByteArray, Bytes, Class, Classmethod, Coroutine, Dict, Exception, ExprResult, FrozenSet,
Function, Int, List, Memoryview, Object, Property, Range, Set, Slice, Staticmethod, Str, Super,
Traceback, Tuple, Type, TypeClass,
Bool, ByteArray, Bytes, Class, Classmethod, Complex, Coroutine, Dict, Exception, ExprResult,
FrozenSet, Function, Int, List, Memoryview, Object, Property, Range, Set, Slice, Staticmethod,
Str, Super, Traceback, Tuple, Type, TypeClass,
};

/// [`Type::Type`] and [`Type::Object`] are excluded here because they are initialized separately.
Expand All @@ -29,6 +29,7 @@ fn builtin_methods() -> HashMap<Type, impl Iterator<Item = Box<dyn Callable>>> {
ReversedIterator::get_methods().into_iter(),
),
(Type::Bytes, Bytes::get_methods().into_iter()),
(Type::Complex, Complex::get_methods().into_iter()),
(Type::ByteArray, ByteArray::get_methods().into_iter()),
(Type::Memoryview, Memoryview::get_methods().into_iter()),
(Type::Coroutine, Coroutine::get_methods().into_iter()),
Expand Down Expand Up @@ -79,6 +80,7 @@ fn all_types() -> Vec<Type> {
Type::Tuple,
Type::Range,
Type::Slice,
Type::Complex,
Type::Bytes,
Type::ByteArray,
Type::Memoryview,
Expand Down Expand Up @@ -126,7 +128,7 @@ fn callable_types() -> Vec<Type> {
Type::Tuple,
Type::Range,
Type::Slice,
//Type::Complex,
Type::Complex,
//Type::Float,
Type::Bytes,
Type::ByteArray,
Expand Down
126 changes: 126 additions & 0 deletions src/treewalk/types/complex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::{
treewalk::{types::builtins::utils::validate_args, Interpreter},
types::errors::InterpreterError,
};
use std::fmt::{Display, Error, Formatter};

use super::{
traits::Callable,
utils::{Dunder, ResolvedArguments},
ExprResult,
};

const DEFAULT_RE: f64 = 0.0;
const DEFAULT_IM: f64 = 0.0;

#[derive(Clone, PartialEq)]
pub struct Complex {
re: f64,
im: f64,
}

impl Complex {
pub fn get_methods() -> Vec<Box<dyn Callable>> {
vec![Box::new(NewBuiltin)]
}

pub fn new(re: f64, im: f64) -> Self {
Self { re, im }
}

fn from_str(input: &str) -> Option<Self> {
// Remove the trailing 'j' character
if !input.ends_with('j') {
return None;
}
let input = &input[..input.len() - 1];

// Find the position of the '+' or '-' sign for the imaginary part
let mut split_pos = None;
for (i, c) in input.char_indices().rev() {
if c == '+' || c == '-' {
split_pos = Some(i);
break;
}
}

let (real_str, imag_str) = input.split_at(split_pos?);

let real_part = real_str.parse::<f64>().ok()?;
let imag_part = imag_str.parse::<f64>().ok()?;

Some(Self::new(real_part, imag_part))
}
}

impl Display for Complex {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
if self.re == DEFAULT_RE {
write!(f, "{}j", self.im)
} else {
write!(f, "({}+{}j)", self.re, self.im)
}
}
}

/// The __new__ method directly creates a complex number with the given parameters. For an
/// immutable built-in type like complex, the __init__ method typically does nothing so we do not
/// need to add it here. This is because the complex object is already fully initialized by the
/// time __init__ is called, and since it is immutable, its state cannot be changed after creation.
struct NewBuiltin;

impl Callable for NewBuiltin {
fn call(
&self,
interpreter: &Interpreter,
args: ResolvedArguments,
) -> Result<ExprResult, InterpreterError> {
let complex = match args.len() {
1 => Complex::new(DEFAULT_RE, DEFAULT_IM),
2 => match args.get_arg(1).as_fp() {
Some(re) => Complex::new(re, DEFAULT_IM),
None => {
let input = &args
.get_arg(1)
.as_string()
.ok_or(InterpreterError::TypeError(
Some(format!(
"complex() first argument must be a string or a number, not '{}'",
args.get_arg(1).get_type()
)),
interpreter.state.call_stack(),
))?;
Complex::from_str(input).ok_or(InterpreterError::TypeError(
None,
interpreter.state.call_stack(),
))?
}
},
3 => {
let re = args
.get_arg(1)
.as_fp()
.ok_or(InterpreterError::ExpectedFloatingPoint(
interpreter.state.call_stack(),
))?;
let im = args
.get_arg(2)
.as_fp()
.ok_or(InterpreterError::ExpectedFloatingPoint(
interpreter.state.call_stack(),
))?;
Complex::new(re, im)
}
_ => {
validate_args(&args, 2, interpreter.state.call_stack())?;
unreachable!()
}
};

Ok(ExprResult::Complex(complex))
}

fn name(&self) -> String {
Dunder::New.into()
}
}
Loading

0 comments on commit f4abe6e

Please sign in to comment.