Skip to content

Commit c4ddff8

Browse files
committed
Implement "{x.attr}".format(...).
1 parent 0a8df91 commit c4ddff8

File tree

1 file changed

+236
-73
lines changed

1 file changed

+236
-73
lines changed

vm/src/format.rs

+236-73
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use crate::function::PyFuncArgs;
22
use crate::obj::objint::PyInt;
33
use crate::obj::objstr::PyString;
44
use crate::obj::{objstr, objtype};
5-
use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, TypeProtocol};
5+
use crate::pyobject::{IntoPyObject, ItemProtocol, PyObjectRef, PyResult, TypeProtocol};
66
use crate::vm::VirtualMachine;
7+
use itertools::{Itertools, PeekingNext};
78
use num_bigint::{BigInt, Sign};
89
use num_traits::cast::ToPrimitive;
910
use num_traits::Signed;
@@ -557,6 +558,9 @@ pub(crate) enum FormatParseError {
557558
MissingStartBracket,
558559
UnescapedStartBracketInLiteral,
559560
InvalidFormatSpecifier,
561+
EmptyAttribute,
562+
MissingRightBracket,
563+
InvalidCharacterAfterRightBracket,
560564
}
561565

562566
impl FromStr for FormatSpec {
@@ -566,25 +570,116 @@ impl FromStr for FormatSpec {
566570
}
567571
}
568572

573+
#[derive(Debug, PartialEq)]
574+
pub(crate) enum FieldNamePart {
575+
Attribute(String),
576+
Index(usize),
577+
StringIndex(String),
578+
}
579+
580+
impl FieldNamePart {
581+
fn parse_part(
582+
chars: &mut impl PeekingNext<Item = char>,
583+
) -> Result<FieldNamePart, FormatParseError> {
584+
let ch = chars.next().unwrap();
585+
if ch == '.' {
586+
let mut attribute = String::new();
587+
for ch in chars.peeking_take_while(|ch| *ch != '.' && *ch != '[') {
588+
attribute.push(ch);
589+
}
590+
if attribute.is_empty() {
591+
Err(FormatParseError::EmptyAttribute)
592+
} else {
593+
Ok(FieldNamePart::Attribute(attribute))
594+
}
595+
} else if ch == '[' {
596+
let mut index = String::new();
597+
for ch in chars {
598+
if ch == ']' {
599+
return if index.is_empty() {
600+
Err(FormatParseError::EmptyAttribute)
601+
} else if let Ok(index) = index.parse::<usize>() {
602+
Ok(FieldNamePart::Index(index))
603+
} else {
604+
Ok(FieldNamePart::StringIndex(index))
605+
};
606+
}
607+
index.push(ch);
608+
}
609+
Err(FormatParseError::MissingRightBracket)
610+
} else {
611+
Err(FormatParseError::InvalidCharacterAfterRightBracket)
612+
}
613+
}
614+
}
615+
616+
#[derive(Debug, PartialEq)]
617+
pub(crate) enum FieldType {
618+
AutoSpec,
619+
IndexSpec(usize),
620+
KeywordSpec(String),
621+
}
622+
623+
#[derive(Debug, PartialEq)]
624+
pub(crate) struct FieldName {
625+
field_type: FieldType,
626+
parts: Vec<FieldNamePart>,
627+
}
628+
629+
impl FieldName {
630+
fn parse(text: &str) -> Result<FieldName, FormatParseError> {
631+
let mut chars = text.chars().peekable();
632+
let mut first = String::new();
633+
for ch in chars.peeking_take_while(|ch| *ch != '.' && *ch != '[') {
634+
first.push(ch);
635+
}
636+
637+
let field_type = if first.is_empty() {
638+
FieldType::AutoSpec
639+
} else if let Ok(index) = first.parse::<usize>() {
640+
FieldType::IndexSpec(index)
641+
} else {
642+
FieldType::KeywordSpec(first)
643+
};
644+
645+
let mut parts = Vec::new();
646+
while chars.peek().is_some() {
647+
parts.push(FieldNamePart::parse_part(&mut chars)?)
648+
}
649+
650+
Ok(FieldName { field_type, parts })
651+
}
652+
}
653+
569654
#[derive(Debug, PartialEq)]
570655
enum FormatPart {
571-
AutoSpec(String),
572-
IndexSpec(usize, String),
573-
KeywordSpec(String, String),
656+
Field(FieldName, String),
574657
Literal(String),
575658
}
576659

577660
impl FormatPart {
578661
fn is_auto(&self) -> bool {
579662
match self {
580-
FormatPart::AutoSpec(_) => true,
663+
FormatPart::Field(
664+
FieldName {
665+
field_type: FieldType::AutoSpec,
666+
..
667+
},
668+
_,
669+
) => true,
581670
_ => false,
582671
}
583672
}
584673

585674
fn is_index(&self) -> bool {
586675
match self {
587-
FormatPart::IndexSpec(_, _) => true,
676+
FormatPart::Field(
677+
FieldName {
678+
field_type: FieldType::IndexSpec(_),
679+
..
680+
},
681+
_,
682+
) => true,
588683
_ => false,
589684
}
590685
}
@@ -655,16 +750,7 @@ impl FormatString {
655750
String::new()
656751
};
657752
let format_spec = preconversor_spec + &format_spec;
658-
659-
if arg_part.is_empty() {
660-
return Ok(FormatPart::AutoSpec(format_spec));
661-
}
662-
663-
if let Ok(index) = arg_part.parse::<usize>() {
664-
Ok(FormatPart::IndexSpec(index, format_spec))
665-
} else {
666-
Ok(FormatPart::KeywordSpec(arg_part.to_owned(), format_spec))
667-
}
753+
Ok(FormatPart::Field(FieldName::parse(arg_part)?, format_spec))
668754
}
669755

670756
fn parse_spec<'a>(
@@ -724,46 +810,34 @@ impl FormatString {
724810
}
725811
}
726812

727-
pub(crate) fn format(&self, arguments: &PyFuncArgs, vm: &VirtualMachine) -> PyResult {
813+
fn format_internal(
814+
&self,
815+
vm: &VirtualMachine,
816+
mut field_func: impl FnMut(&FieldType) -> PyResult,
817+
) -> PyResult {
728818
let mut final_string = String::new();
729-
if self.format_parts.iter().any(FormatPart::is_auto)
730-
&& self.format_parts.iter().any(FormatPart::is_index)
731-
{
732-
return Err(vm.new_value_error(
733-
"cannot switch from automatic field numbering to manual field specification"
734-
.to_owned(),
735-
));
736-
}
737-
let mut auto_argument_index: usize = 1;
738819
for part in &self.format_parts {
739820
let result_string: String = match part {
740-
FormatPart::AutoSpec(format_spec) => {
741-
let result = match arguments.args.get(auto_argument_index) {
742-
Some(argument) => call_object_format(vm, argument.clone(), &format_spec)?,
743-
None => {
744-
return Err(vm.new_index_error("tuple index out of range".to_owned()));
745-
}
746-
};
747-
auto_argument_index += 1;
748-
objstr::clone_value(&result)
749-
}
750-
FormatPart::IndexSpec(index, format_spec) => {
751-
let result = match arguments.args.get(*index + 1) {
752-
Some(argument) => call_object_format(vm, argument.clone(), &format_spec)?,
753-
None => {
754-
return Err(vm.new_index_error("tuple index out of range".to_owned()));
755-
}
756-
};
757-
objstr::clone_value(&result)
758-
}
759-
FormatPart::KeywordSpec(keyword, format_spec) => {
760-
let result = match arguments.get_optional_kwarg(&keyword) {
761-
Some(argument) => call_object_format(vm, argument.clone(), &format_spec)?,
762-
None => {
763-
return Err(vm.new_key_error(vm.new_str(keyword.to_owned())));
821+
FormatPart::Field(FieldName { field_type, parts }, format_spec) => {
822+
let mut argument = field_func(field_type)?;
823+
824+
for name_part in parts {
825+
match name_part {
826+
FieldNamePart::Attribute(attribute) => {
827+
argument = vm.get_attribute(argument, attribute.as_str())?;
828+
}
829+
FieldNamePart::Index(index) => {
830+
// TODO Implement DictKey for usize so we can pass index directly
831+
argument = argument.get_item(&index.into_pyobject(vm)?, vm)?;
832+
}
833+
FieldNamePart::StringIndex(index) => {
834+
argument = argument.get_item(index, vm)?;
835+
}
764836
}
765-
};
766-
objstr::clone_value(&result)
837+
}
838+
839+
let value = call_object_format(vm, argument, &format_spec)?;
840+
objstr::clone_value(&value)
767841
}
768842
FormatPart::Literal(literal) => literal.clone(),
769843
};
@@ -772,25 +846,44 @@ impl FormatString {
772846
Ok(vm.ctx.new_str(final_string))
773847
}
774848

775-
pub(crate) fn format_map(&self, dict: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
776-
let mut final_string = String::new();
777-
for part in &self.format_parts {
778-
let result_string: String = match part {
779-
FormatPart::AutoSpec(_) | FormatPart::IndexSpec(_, _) => {
780-
return Err(
781-
vm.new_value_error("Format string contains positional fields".to_owned())
782-
);
783-
}
784-
FormatPart::KeywordSpec(keyword, format_spec) => {
785-
let argument = dict.get_item(keyword, &vm)?;
786-
let result = call_object_format(vm, argument.clone(), &format_spec)?;
787-
objstr::clone_value(&result)
788-
}
789-
FormatPart::Literal(literal) => literal.clone(),
790-
};
791-
final_string.push_str(&result_string);
849+
pub(crate) fn format(&self, arguments: &PyFuncArgs, vm: &VirtualMachine) -> PyResult {
850+
if self.format_parts.iter().any(FormatPart::is_auto)
851+
&& self.format_parts.iter().any(FormatPart::is_index)
852+
{
853+
return Err(vm.new_value_error(
854+
"cannot switch from automatic field numbering to manual field specification"
855+
.to_owned(),
856+
));
792857
}
793-
Ok(vm.ctx.new_str(final_string))
858+
859+
let mut auto_argument_index: usize = 1;
860+
self.format_internal(vm, |field_type| match field_type {
861+
FieldType::AutoSpec => {
862+
auto_argument_index += 1;
863+
arguments
864+
.args
865+
.get(auto_argument_index - 1)
866+
.cloned()
867+
.ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned()))
868+
}
869+
FieldType::IndexSpec(index) => arguments
870+
.args
871+
.get(*index + 1)
872+
.cloned()
873+
.ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned())),
874+
FieldType::KeywordSpec(keyword) => arguments
875+
.get_optional_kwarg(&keyword)
876+
.ok_or_else(|| vm.new_key_error(vm.new_str(keyword.to_owned()))),
877+
})
878+
}
879+
880+
pub(crate) fn format_map(&self, dict: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
881+
self.format_internal(vm, |field_type| match field_type {
882+
FieldType::AutoSpec | FieldType::IndexSpec(_) => {
883+
Err(vm.new_value_error("Format string contains positional fields".to_owned()))
884+
}
885+
FieldType::KeywordSpec(keyword) => dict.get_item(keyword, &vm),
886+
})
794887
}
795888
}
796889

@@ -968,9 +1061,21 @@ mod tests {
9681061
let expected = Ok(FormatString {
9691062
format_parts: vec![
9701063
FormatPart::Literal("abcd".to_owned()),
971-
FormatPart::IndexSpec(1, String::new()),
1064+
FormatPart::Field(
1065+
FieldName {
1066+
field_type: FieldType::IndexSpec(1),
1067+
parts: Vec::new(),
1068+
},
1069+
String::new(),
1070+
),
9721071
FormatPart::Literal(":".to_owned()),
973-
FormatPart::KeywordSpec("key".to_owned(), String::new()),
1072+
FormatPart::Field(
1073+
FieldName {
1074+
field_type: FieldType::KeywordSpec("key".to_owned()),
1075+
parts: Vec::new(),
1076+
},
1077+
String::new(),
1078+
),
9741079
],
9751080
});
9761081

@@ -993,7 +1098,13 @@ mod tests {
9931098
let expected = Ok(FormatString {
9941099
format_parts: vec![
9951100
FormatPart::Literal("{".to_owned()),
996-
FormatPart::KeywordSpec("key".to_owned(), String::new()),
1101+
FormatPart::Field(
1102+
FieldName {
1103+
field_type: FieldType::KeywordSpec("key".to_owned()),
1104+
parts: Vec::new(),
1105+
},
1106+
String::new(),
1107+
),
9971108
FormatPart::Literal("}ddfe".to_owned()),
9981109
],
9991110
});
@@ -1014,4 +1125,56 @@ mod tests {
10141125
assert_eq!(parse_format_spec("o!"), Err("Invalid format specifier"));
10151126
assert_eq!(parse_format_spec("d "), Err("Invalid format specifier"));
10161127
}
1128+
1129+
#[test]
1130+
fn test_parse_field_name() {
1131+
assert_eq!(
1132+
FieldName::parse(""),
1133+
Ok(FieldName {
1134+
field_type: FieldType::AutoSpec,
1135+
parts: Vec::new(),
1136+
})
1137+
);
1138+
assert_eq!(
1139+
FieldName::parse("0"),
1140+
Ok(FieldName {
1141+
field_type: FieldType::IndexSpec(0),
1142+
parts: Vec::new(),
1143+
})
1144+
);
1145+
assert_eq!(
1146+
FieldName::parse("key"),
1147+
Ok(FieldName {
1148+
field_type: FieldType::KeywordSpec("key".to_owned()),
1149+
parts: Vec::new(),
1150+
})
1151+
);
1152+
assert_eq!(
1153+
FieldName::parse("key.attr[0][string]"),
1154+
Ok(FieldName {
1155+
field_type: FieldType::KeywordSpec("key".to_owned()),
1156+
parts: vec![
1157+
FieldNamePart::Attribute("attr".to_owned()),
1158+
FieldNamePart::Index(0),
1159+
FieldNamePart::StringIndex("string".to_owned())
1160+
],
1161+
})
1162+
);
1163+
assert_eq!(
1164+
FieldName::parse("key.."),
1165+
Err(FormatParseError::EmptyAttribute)
1166+
);
1167+
assert_eq!(
1168+
FieldName::parse("key[]"),
1169+
Err(FormatParseError::EmptyAttribute)
1170+
);
1171+
assert_eq!(
1172+
FieldName::parse("key["),
1173+
Err(FormatParseError::MissingRightBracket)
1174+
);
1175+
assert_eq!(
1176+
FieldName::parse("key[0]after"),
1177+
Err(FormatParseError::InvalidCharacterAfterRightBracket)
1178+
);
1179+
}
10171180
}

0 commit comments

Comments
 (0)