diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index 2661d6f..f1f7f77 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -219,6 +219,8 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( ) } + 0x24 => (input, Instruction::StringSize), + _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), }) } @@ -620,7 +622,7 @@ mod tests { #[test] fn test_instructions() { let input = &[ - 0x24, // list of 36 items + 0x25, // list of 37 items 0x00, 0x01, // ArgumentGet { index: 1 } 0x01, 0x01, // CallCore { function_index: 1 } 0x02, // S8FromI32 @@ -657,6 +659,7 @@ mod tests { 0x21, // I64FromU64 0x22, // StringLiftMemory 0x23, 0x01, // StringLowerMemory { allocator_index: 1 } + 0x24, // StringSize 0x0a, ]; let output = Ok(( @@ -698,6 +701,7 @@ mod tests { Instruction::I64FromU64, Instruction::StringLiftMemory, Instruction::StringLowerMemory { allocator_index: 1 }, + Instruction::StringSize, ], )); diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs index a3469ea..3e41d51 100644 --- a/src/decoders/wat.rs +++ b/src/decoders/wat.rs @@ -62,6 +62,7 @@ mod keyword { custom_keyword!(i64_from_u64 = "i64.from_u64"); custom_keyword!(string_lift_memory = "string.lift_memory"); custom_keyword!(string_lower_memory = "string.lower_memory"); + custom_keyword!(string_size = "string.size"); } impl Parse<'_> for InterfaceType { @@ -285,6 +286,10 @@ impl<'a> Parse<'a> for Instruction { Ok(Instruction::StringLowerMemory { allocator_index: parser.parse()?, }) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::StringSize) } else { Err(lookahead.error()) } @@ -666,6 +671,7 @@ mod tests { "i64.from_u64", "string.lift_memory", "string.lower_memory 42", + "string.size", ]; let outputs = vec![ Instruction::ArgumentGet { index: 7 }, @@ -706,6 +712,7 @@ mod tests { Instruction::StringLowerMemory { allocator_index: 42, }, + Instruction::StringSize, ]; assert_eq!(inputs.len(), outputs.len()); diff --git a/src/encoders/binary.rs b/src/encoders/binary.rs index aece560..7a24669 100644 --- a/src/encoders/binary.rs +++ b/src/encoders/binary.rs @@ -299,6 +299,8 @@ where 0x23_u8.to_bytes(writer)?; (*allocator_index as u64).to_bytes(writer)?; } + + Instruction::StringSize => 0x24_u8.to_bytes(writer)?, } Ok(()) @@ -577,9 +579,10 @@ mod tests { Instruction::I64FromU64, Instruction::StringLiftMemory, Instruction::StringLowerMemory { allocator_index: 1 }, + Instruction::StringSize, ], &[ - 0x24, // list of 36 items + 0x25, // list of 37 items 0x00, 0x01, // ArgumentGet { index: 1 } 0x01, 0x01, // CallCore { function_index: 1 } 0x02, // S8FromI32 @@ -616,6 +619,7 @@ mod tests { 0x21, // I64FromU64 0x22, // StringLiftMemory 0x23, 0x01, // StringLowerMemory { allocator_index: 1 } + 0x24, // StringSize ] ); } diff --git a/src/encoders/wat.rs b/src/encoders/wat.rs index 4914225..7856e59 100644 --- a/src/encoders/wat.rs +++ b/src/encoders/wat.rs @@ -121,6 +121,7 @@ impl ToString for &Instruction { Instruction::StringLowerMemory { allocator_index } => { format!(r#"string.lower_memory {}"#, allocator_index) } + Instruction::StringSize => "string.size".into(), } } } @@ -391,6 +392,7 @@ mod tests { allocator_index: 42, }) .to_string(), + (&Instruction::StringSize).to_string(), ]; let outputs = vec![ "arg.get 7", @@ -429,6 +431,7 @@ mod tests { "i64.from_u64", "string.lift_memory", "string.lower_memory 42", + "string.size", ]; assert_eq!(inputs, outputs); diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 0ef4c94..712a6ea 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -119,4 +119,7 @@ pub enum Instruction { /// The allocator function index. allocator_index: u32, }, + + /// The `string.size` instruction. + StringSize, } diff --git a/src/interpreter/instructions/strings.rs b/src/interpreter/instructions/strings.rs index a64547b..011cac9 100644 --- a/src/interpreter/instructions/strings.rs +++ b/src/interpreter/instructions/strings.rs @@ -135,6 +135,34 @@ executable_instruction!( } ); +executable_instruction!( + string_size(instruction: Instruction) -> _ { + move |runtime| -> _ { + let value = runtime.stack.peek1().ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 }, + ) + })?; + + if let InterfaceValue::String(string) = value { + let length = string.len() as i32; + runtime.stack.push(InterfaceValue::I32(length)); + + Ok(()) + } else { + Err(InstructionError::new( + instruction, + InstructionErrorKind::InvalidValueOnTheStack { + expected_type: InterfaceType::String, + received_type: value.into(), + } + )) + } + } + } +); + #[cfg(test)] mod tests { test_executable_instruction!( @@ -323,4 +351,36 @@ mod tests { }, error: r#"`string.lower_memory 153` the local or import function `153` has the signature `[I32] -> [I32]` but it received values of kind `[I32, I32] -> []`"#, ); + + test_executable_instruction!( + test_string_size = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringSize, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [InterfaceValue::String("Hello, World!".into()), InterfaceValue::I32(13)], + ); + + test_executable_instruction!( + test_string_size__stack_is_too_small = + instructions: [ + Instruction::StringSize, + ], + invocation_inputs: [], + instance: Instance::new(), + error: r#"`string.size` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#, + ); + + test_executable_instruction!( + test_string_size__invalid_value_on_the_stack = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringSize, + ], + invocation_inputs: [InterfaceValue::I32(42)], + instance: Instance::new(), + error: r#"`string.size` read a value of type `I32` from the stack, but the type `String` was expected"#, + ); } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 560928f..22252ae 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -234,6 +234,7 @@ where Instruction::StringLowerMemory { allocator_index } => { instructions::string_lower_memory(*allocator_index, *instruction) } + Instruction::StringSize => instructions::string_size(*instruction), }) .collect();