1 //! # Scroll 2 //! 3 //! ```text, no_run 4 //! _______________ 5 //! ()==( (@==() 6 //! '______________'| 7 //! | | 8 //! | ἀρετή | 9 //! __)_____________| 10 //! ()==( (@==() 11 //! '--------------' 12 //! 13 //! ``` 14 //! 15 //! Scroll is a library for efficiently and easily reading/writing types from byte arrays. All the builtin types are supported, e.g., `u32`, `i8`, etc., where the type is specified as a type parameter, or type inferred when possible. In addition, it supports zero-copy reading of string slices, or any other kind of slice. The library can be used in a no_std context as well; the [Error](enum.Error.html) type only has the `IO` and `String` variants if the default features are used, and is `no_std` safe when compiled without default features. 16 //! 17 //! There are 3 traits for reading that you can import: 18 //! 19 //! 1. [Pread](trait.Pread.html), for reading (immutable) data at an offset; 20 //! 2. [Gread](trait.Gread.html), for reading data at an offset which automatically gets incremented by the size; 21 //! 3. [IOread](trait.IOread.html), for reading _simple_ data out of a `std::io::Read` based interface, e.g., a stream. (**Note**: only available when compiled with `std`) 22 //! 23 //! Each of these interfaces also have their corresponding writer versions as well, e.g., [Pwrite](trait.Pwrite.html), [Gwrite](trait.Gwrite.html), and [IOwrite](trait.IOwrite.html), respectively. 24 //! 25 //! Most familiar will likely be the `Pread` trait (inspired from the C function), which in our case takes an immutable reference to self, an immutable offset to read at, (and _optionally_ a parsing context, more on that later), and then returns the deserialized value. 26 //! 27 //! Because self is immutable, _**all** reads can be performed in parallel_ and hence are trivially parallelizable. 28 //! 29 //! For most usecases, you can use [scroll_derive](https://docs.rs/scroll_derive) to annotate your types with `derive(Pread, Pwrite, IOread, IOwrite, SizeWith)` to automatically add sensible derive defaults, and you should be ready to roll. For more complex usescases, you can implement the conversion traits yourself, see the [context module](ctx/index.html) for more information. 30 //! 31 //! # Example 32 //! 33 //! A simple example demonstrates its flexibility: 34 //! 35 //! ```rust 36 //! use scroll::{ctx, Pread, LE}; 37 //! let bytes: [u8; 4] = [0xde, 0xad, 0xbe, 0xef]; 38 //! 39 //! // reads a u32 out of `b` with the endianness of the host machine, at offset 0, turbofish-style 40 //! let number: u32 = bytes.pread::<u32>(0).unwrap(); 41 //! // ...or a byte, with type ascription on the binding. 42 //! let byte: u8 = bytes.pread(0).unwrap(); 43 //! 44 //! //If the type is known another way by the compiler, say reading into a struct field, we can omit the turbofish, and type ascription altogether! 45 //! 46 //! // If we want, we can explicitly add a endianness to read with by calling `pread_with`. 47 //! // The following reads a u32 out of `b` with Big Endian byte order, at offset 0 48 //! let be_number: u32 = bytes.pread_with(0, scroll::BE).unwrap(); 49 //! // or a u16 - specify the type either on the variable or with the beloved turbofish 50 //! let be_number2 = bytes.pread_with::<u16>(2, scroll::BE).unwrap(); 51 //! 52 //! // Scroll has core friendly errors (no allocation). This will have the type `scroll::Error::BadOffset` because it tried to read beyond the bound 53 //! let byte: scroll::Result<i64> = bytes.pread(0); 54 //! 55 //! // Scroll is extensible: as long as the type implements `TryWithCtx`, then you can read your type out of the byte array! 56 //! 57 //! // We can parse out custom datatypes, or types with lifetimes 58 //! // if they implement the conversion trait `TryFromCtx`; here we parse a C-style \0 delimited &str (safely) 59 //! let hello: &[u8] = b"hello_world\0more words"; 60 //! let hello_world: &str = hello.pread(0).unwrap(); 61 //! assert_eq!("hello_world", hello_world); 62 //! 63 //! // ... and this parses the string if its space separated! 64 //! use scroll::ctx::*; 65 //! let spaces: &[u8] = b"hello world some junk"; 66 //! let world: &str = spaces.pread_with(6, StrCtx::Delimiter(SPACE)).unwrap(); 67 //! assert_eq!("world", world); 68 //! ``` 69 //! 70 //! # `std::io` API 71 //! 72 //! Scroll can also read/write simple types from a `std::io::Read` or `std::io::Write` implementor. The built-in numeric types are taken care of for you. If you want to read a custom type, you need to implement the [FromCtx](trait.FromCtx.html) (_how_ to parse) and [SizeWith](ctx/trait.SizeWith.html) (_how_ big the parsed thing will be) traits. You must compile with default features. For example: 73 //! 74 //! ```rust 75 //! use std::io::Cursor; 76 //! use scroll::IOread; 77 //! let bytes_ = [0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xef,0xbe,0x00,0x00,]; 78 //! let mut bytes = Cursor::new(bytes_); 79 //! 80 //! // this will bump the cursor's Seek 81 //! let foo = bytes.ioread::<u64>().unwrap(); 82 //! // ..ditto 83 //! let bar = bytes.ioread::<u32>().unwrap(); 84 //! ``` 85 //! 86 //! Similarly, we can write to anything that implements `std::io::Write` quite naturally: 87 //! 88 //! ```rust 89 //! use scroll::{IOwrite, LE, BE}; 90 //! use std::io::{Write, Cursor}; 91 //! 92 //! let mut bytes = [0x0u8; 10]; 93 //! let mut cursor = Cursor::new(&mut bytes[..]); 94 //! cursor.write_all(b"hello").unwrap(); 95 //! cursor.iowrite_with(0xdeadbeef as u32, BE).unwrap(); 96 //! assert_eq!(cursor.into_inner(), [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xde, 0xad, 0xbe, 0xef, 0x0]); 97 //! ``` 98 //! 99 //! # Advanced Uses 100 //! 101 //! Scroll is designed to be highly configurable - it allows you to implement various context (`Ctx`) sensitive traits, which then grants the implementor _automatic_ uses of the `Pread` and/or `Pwrite` traits. 102 //! 103 //! For example, suppose we have a datatype and we want to specify how to parse or serialize this datatype out of some arbitrary 104 //! byte buffer. In order to do this, we need to provide a [TryFromCtx](trait.TryFromCtx.html) impl for our datatype. 105 //! 106 //! In particular, if we do this for the `[u8]` target, using the convention `(usize, YourCtx)`, you will automatically get access to 107 //! calling `pread_with::<YourDatatype>` on arrays of bytes. 108 //! 109 //! ```rust 110 //! use scroll::{self, ctx, Pread, BE, Endian}; 111 //! 112 //! struct Data<'a> { 113 //! name: &'a str, 114 //! id: u32, 115 //! } 116 //! 117 //! // note the lifetime specified here 118 //! impl<'a> ctx::TryFromCtx<'a, Endian> for Data<'a> { 119 //! type Error = scroll::Error; 120 //! type Size = usize; 121 //! // and the lifetime annotation on `&'a [u8]` here 122 //! fn try_from_ctx (src: &'a [u8], endian: Endian) 123 //! -> Result<(Self, Self::Size), Self::Error> { 124 //! let offset = &mut 0; 125 //! let name = src.gread::<&str>(offset)?; 126 //! let id = src.gread_with(offset, endian)?; 127 //! Ok((Data { name: name, id: id }, *offset)) 128 //! } 129 //! } 130 //! 131 //! let bytes = b"UserName\x00\x01\x02\x03\x04"; 132 //! let data = bytes.pread_with::<Data>(0, BE).unwrap(); 133 //! assert_eq!(data.id, 0x01020304); 134 //! assert_eq!(data.name.to_string(), "UserName".to_string()); 135 //! ``` 136 //! 137 //! Please see the [Pread documentation examples](trait.Pread.html#implementing-your-own-reader) 138 139 #![cfg_attr(not(feature = "std"), no_std)] 140 141 #[cfg(feature = "derive")] 142 #[allow(unused_imports)] 143 #[macro_use] 144 extern crate scroll_derive; 145 146 #[cfg(feature = "derive")] 147 #[doc(hidden)] 148 pub use scroll_derive::*; 149 150 #[cfg(feature = "std")] 151 extern crate core; 152 153 pub mod ctx; 154 mod pread; 155 mod pwrite; 156 mod greater; 157 mod error; 158 mod endian; 159 mod leb128; 160 #[cfg(feature = "std")] 161 mod lesser; 162 163 pub use endian::*; 164 pub use pread::*; 165 pub use pwrite::*; 166 pub use greater::*; 167 pub use error::*; 168 pub use leb128::*; 169 #[cfg(feature = "std")] 170 pub use lesser::*; 171 172 #[doc(hidden)] 173 pub mod export { 174 pub use ::core::result; 175 pub use ::core::mem; 176 } 177 178 #[cfg(test)] 179 mod tests { 180 #[allow(overflowing_literals)] 181 use super::{LE}; 182 183 #[test] test_measure_with_bytes()184 fn test_measure_with_bytes() { 185 use super::ctx::MeasureWith; 186 let bytes: [u8; 4] = [0xef, 0xbe, 0xad, 0xde]; 187 assert_eq!(bytes.measure_with(&()), 4); 188 } 189 190 #[test] test_measurable()191 fn test_measurable() { 192 use super::ctx::SizeWith; 193 assert_eq!(8, u64::size_with(&LE)); 194 } 195 196 ////////////////////////////////////////////////////////////// 197 // begin pread_with 198 ////////////////////////////////////////////////////////////// 199 200 macro_rules! pwrite_test { 201 ($write:ident, $read:ident, $deadbeef:expr) => { 202 #[test] 203 fn $write() { 204 use super::{Pwrite, Pread, BE}; 205 let mut bytes: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; 206 let b = &mut bytes[..]; 207 b.pwrite_with::<$read>($deadbeef, 0, LE).unwrap(); 208 assert_eq!(b.pread_with::<$read>(0, LE).unwrap(), $deadbeef); 209 b.pwrite_with::<$read>($deadbeef, 0, BE).unwrap(); 210 assert_eq!(b.pread_with::<$read>(0, BE).unwrap(), $deadbeef); 211 } 212 } 213 } 214 215 pwrite_test!(pwrite_and_pread_roundtrip_u16, u16, 0xbeef); 216 pwrite_test!(pwrite_and_pread_roundtrip_i16, i16, 0x7eef); 217 pwrite_test!(pwrite_and_pread_roundtrip_u32, u32, 0xbeefbeef); 218 pwrite_test!(pwrite_and_pread_roundtrip_i32, i32, 0x7eefbeef); 219 pwrite_test!(pwrite_and_pread_roundtrip_u64, u64, 0xbeefbeef7eef7eef); 220 pwrite_test!(pwrite_and_pread_roundtrip_i64, i64, 0x7eefbeef7eef7eef); 221 222 #[test] pread_with_be()223 fn pread_with_be() { 224 use super::{Pread}; 225 let bytes: [u8; 2] = [0x7e, 0xef]; 226 let b = &bytes[..]; 227 let byte: u16 = b.pread_with(0, super::BE).unwrap(); 228 assert_eq!(0x7eef, byte); 229 let bytes: [u8; 2] = [0xde, 0xad]; 230 let dead: u16 = bytes.pread_with(0, super::BE).unwrap(); 231 assert_eq!(0xdead, dead); 232 } 233 234 #[test] pread()235 fn pread() { 236 use super::{Pread}; 237 let bytes: [u8; 2] = [0x7e, 0xef]; 238 let b = &bytes[..]; 239 let byte: u16 = b.pread(0).unwrap(); 240 #[cfg(target_endian = "little")] 241 assert_eq!(0xef7e, byte); 242 #[cfg(target_endian = "big")] 243 assert_eq!(0x7eef, byte); 244 } 245 246 #[test] pread_slice()247 fn pread_slice() { 248 use super::{Pread}; 249 use super::ctx::StrCtx; 250 let bytes: [u8; 2] = [0x7e, 0xef]; 251 let b = &bytes[..]; 252 let iserr: Result<&str, _> = b.pread_with(0, StrCtx::Length(3)); 253 assert!(iserr.is_err()); 254 // let bytes2: &[u8] = b.pread_with(0, 2).unwrap(); 255 // assert_eq!(bytes2.len(), bytes[..].len()); 256 // for i in 0..bytes2.len() { 257 // assert_eq!(bytes2[i], bytes[i]) 258 // } 259 } 260 261 #[test] pread_str()262 fn pread_str() { 263 use super::Pread; 264 use super::ctx::*; 265 let bytes: [u8; 2] = [0x2e, 0x0]; 266 let b = &bytes[..]; 267 let s: &str = b.pread(0).unwrap(); 268 println!("str: {}", s); 269 assert_eq!(s.len(), bytes[..].len() - 1); 270 let bytes: &[u8] = b"hello, world!\0some_other_things"; 271 let hello_world: &str = bytes.pread_with(0, StrCtx::Delimiter(NULL)).unwrap(); 272 println!("{:?}", &hello_world); 273 assert_eq!(hello_world.len(), 13); 274 let hello: &str = bytes.pread_with(0, StrCtx::Delimiter(SPACE)).unwrap(); 275 println!("{:?}", &hello); 276 assert_eq!(hello.len(), 6); 277 // this could result in underflow so we just try it 278 let _error = bytes.pread_with::<&str>(6, StrCtx::Delimiter(SPACE)); 279 let error = bytes.pread_with::<&str>(7, StrCtx::Delimiter(SPACE)); 280 println!("{:?}", &error); 281 assert!(error.is_ok()); 282 } 283 284 #[test] pread_str_weird()285 fn pread_str_weird() { 286 use super::Pread; 287 use super::ctx::*; 288 let bytes: &[u8] = b""; 289 let hello_world = bytes.pread_with::<&str>(0, StrCtx::Delimiter(NULL)); 290 println!("1 {:?}", &hello_world); 291 assert_eq!(hello_world.is_err(), true); 292 let error = bytes.pread_with::<&str>(7, StrCtx::Delimiter(SPACE)); 293 println!("2 {:?}", &error); 294 assert!(error.is_err()); 295 let bytes: &[u8] = b"\0"; 296 let null = bytes.pread::<&str>(0).unwrap(); 297 println!("3 {:?}", &null); 298 assert_eq!(null.len(), 0); 299 } 300 301 #[test] pwrite_str_and_bytes()302 fn pwrite_str_and_bytes() { 303 use super::{Pread, Pwrite}; 304 use super::ctx::*; 305 let astring: &str = "lol hello_world lal\0ala imabytes"; 306 let mut buffer = [0u8; 33]; 307 buffer.pwrite(astring, 0).unwrap(); 308 { 309 let hello_world = buffer.pread_with::<&str>(4, StrCtx::Delimiter(SPACE)).unwrap(); 310 assert_eq!(hello_world, "hello_world"); 311 } 312 let bytes: &[u8] = b"more\0bytes"; 313 buffer.pwrite(bytes, 0).unwrap(); 314 let more = bytes.pread_with::<&str>(0, StrCtx::Delimiter(NULL)).unwrap(); 315 assert_eq!(more, "more"); 316 let bytes = bytes.pread_with::<&str>(more.len() + 1, StrCtx::Delimiter(NULL)).unwrap(); 317 assert_eq!(bytes, "bytes"); 318 } 319 320 use std::error; 321 use std::fmt::{self, Display}; 322 323 #[derive(Debug)] 324 pub struct ExternalError {} 325 326 impl Display for ExternalError { fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result327 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 328 write!(fmt, "ExternalError") 329 } 330 } 331 332 impl error::Error for ExternalError { description(&self) -> &str333 fn description(&self) -> &str { 334 "ExternalError" 335 } cause(&self) -> Option<&error::Error>336 fn cause(&self) -> Option<&error::Error> { None} 337 } 338 339 impl From<super::Error> for ExternalError { from(err: super::Error) -> Self340 fn from(err: super::Error) -> Self { 341 //use super::Error::*; 342 match err { 343 _ => ExternalError{}, 344 } 345 } 346 } 347 348 #[derive(Debug, PartialEq, Eq)] 349 pub struct Foo(u16); 350 351 impl super::ctx::TryIntoCtx<super::Endian> for Foo { 352 type Error = ExternalError; 353 type Size = usize; try_into_ctx(self, this: &mut [u8], le: super::Endian) -> Result<Self::Size, Self::Error>354 fn try_into_ctx(self, this: &mut [u8], le: super::Endian) -> Result<Self::Size, Self::Error> { 355 use super::Pwrite; 356 if this.len() < 2 { return Err((ExternalError {}).into()) } 357 this.pwrite_with(self.0, 0, le)?; 358 Ok(2) 359 } 360 } 361 362 impl<'a> super::ctx::TryFromCtx<'a, super::Endian> for Foo { 363 type Error = ExternalError; 364 type Size = usize; try_from_ctx(this: &'a [u8], le: super::Endian) -> Result<(Self, Self::Size), Self::Error>365 fn try_from_ctx(this: &'a [u8], le: super::Endian) -> Result<(Self, Self::Size), Self::Error> { 366 use super::Pread; 367 if this.len() > 2 { return Err((ExternalError {}).into()) } 368 let n = this.pread_with(0, le)?; 369 Ok((Foo(n), 2)) 370 } 371 } 372 373 #[test] pread_with_iter_bytes()374 fn pread_with_iter_bytes() { 375 use super::{Pread}; 376 let mut bytes_to: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; 377 let bytes_from: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; 378 let bytes_to = &mut bytes_to[..]; 379 let bytes_from = &bytes_from[..]; 380 for i in 0..bytes_from.len() { 381 bytes_to[i] = bytes_from.pread(i).unwrap(); 382 } 383 assert_eq!(bytes_to, bytes_from); 384 } 385 386 ////////////////////////////////////////////////////////////// 387 // end pread_with 388 ////////////////////////////////////////////////////////////// 389 390 ////////////////////////////////////////////////////////////// 391 // begin gread_with 392 ////////////////////////////////////////////////////////////// 393 macro_rules! g_test { 394 ($read:ident, $deadbeef:expr, $typ:ty) => { 395 #[test] 396 fn $read() { 397 use super::Pread; 398 let bytes: [u8; 8] = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd]; 399 let mut offset = 0; 400 let deadbeef: $typ = bytes.gread_with(&mut offset, LE).unwrap(); 401 assert_eq!(deadbeef, $deadbeef as $typ); 402 assert_eq!(offset, ::std::mem::size_of::<$typ>()); 403 } 404 } 405 } 406 407 g_test!(simple_gread_u16, 0xe0f, u16); 408 g_test!(simple_gread_u32, 0xb0e0e0f, u32); 409 g_test!(simple_gread_u64, 0xd0e0a0d0b0e0e0f, u64); 410 g_test!(simple_gread_i64, 940700423303335439, i64); 411 412 macro_rules! simple_float_test { 413 ($read:ident, $deadbeef:expr, $typ:ty) => { 414 #[test] 415 fn $read() { 416 use super::Pread; 417 let bytes: [u8; 8] = [0u8, 0, 0, 0, 0, 0, 224, 63]; 418 let mut offset = 0; 419 let deadbeef: $typ = bytes.gread_with(&mut offset, LE).unwrap(); 420 assert_eq!(deadbeef, $deadbeef as $typ); 421 assert_eq!(offset, ::std::mem::size_of::<$typ>()); 422 } 423 }; 424 } 425 426 simple_float_test!(gread_f32, 0.0, f32); 427 simple_float_test!(gread_f64, 0.5, f64); 428 429 macro_rules! g_read_write_test { 430 ($read:ident, $val:expr, $typ:ty) => { 431 #[test] 432 fn $read() { 433 use super::{LE, BE, Pread, Pwrite}; 434 let mut buffer = [0u8; 16]; 435 let offset = &mut 0; 436 buffer.gwrite_with($val.clone(), offset, LE).unwrap(); 437 let o2 = &mut 0; 438 let val: $typ = buffer.gread_with(o2, LE).unwrap(); 439 assert_eq!(val, $val); 440 assert_eq!(*offset, ::std::mem::size_of::<$typ>()); 441 assert_eq!(*o2, ::std::mem::size_of::<$typ>()); 442 assert_eq!(*o2, *offset); 443 buffer.gwrite_with($val.clone(), offset, BE).unwrap(); 444 let val: $typ = buffer.gread_with(o2, BE).unwrap(); 445 assert_eq!(val, $val); 446 } 447 }; 448 } 449 450 g_read_write_test!(gread_gwrite_f64_1, 0.25f64, f64); 451 g_read_write_test!(gread_gwrite_f64_2, 0.5f64, f64); 452 g_read_write_test!(gread_gwrite_f64_3, 0.064, f64); 453 454 g_read_write_test!(gread_gwrite_f32_1, 0.25f32, f32); 455 g_read_write_test!(gread_gwrite_f32_2, 0.5f32, f32); 456 g_read_write_test!(gread_gwrite_f32_3, 0.0f32, f32); 457 458 g_read_write_test!(gread_gwrite_i64_1, 0i64, i64); 459 g_read_write_test!(gread_gwrite_i64_2, -1213213211111i64, i64); 460 g_read_write_test!(gread_gwrite_i64_3, -3000i64, i64); 461 462 g_read_write_test!(gread_gwrite_i32_1, 0i32, i32); 463 g_read_write_test!(gread_gwrite_i32_2, -1213213232, i32); 464 g_read_write_test!(gread_gwrite_i32_3, -3000i32, i32); 465 466 // useful for ferreting out problems with impls 467 #[test] gread_with_iter_bytes()468 fn gread_with_iter_bytes() { 469 use super::{Pread}; 470 let mut bytes_to: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; 471 let bytes_from: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; 472 let bytes_to = &mut bytes_to[..]; 473 let bytes_from = &bytes_from[..]; 474 let mut offset = &mut 0; 475 for i in 0..bytes_from.len() { 476 bytes_to[i] = bytes_from.gread(&mut offset).unwrap(); 477 } 478 assert_eq!(bytes_to, bytes_from); 479 assert_eq!(*offset, bytes_to.len()); 480 } 481 482 #[test] gread_inout()483 fn gread_inout() { 484 use super::{Pread}; 485 let mut bytes_to: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; 486 let bytes_from: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; 487 let bytes = &bytes_from[..]; 488 let offset = &mut 0; 489 bytes.gread_inout(offset, &mut bytes_to[..]).unwrap(); 490 assert_eq!(bytes_to, bytes_from); 491 assert_eq!(*offset, bytes_to.len()); 492 } 493 494 #[test] gread_with_byte()495 fn gread_with_byte() { 496 use super::{Pread}; 497 let bytes: [u8; 1] = [0x7f]; 498 let b = &bytes[..]; 499 let offset = &mut 0; 500 let byte: u8 = b.gread(offset).unwrap(); 501 assert_eq!(0x7f, byte); 502 assert_eq!(*offset, 1); 503 } 504 505 #[test] gread_slice()506 fn gread_slice() { 507 use super::{Pread}; 508 use super::ctx::{StrCtx}; 509 let bytes: [u8; 2] = [0x7e, 0xef]; 510 let b = &bytes[..]; 511 let offset = &mut 0; 512 let res = b.gread_with::<&str>(offset, StrCtx::Length(3)); 513 assert!(res.is_err()); 514 *offset = 0; 515 let astring: [u8; 3] = [0x45, 042, 0x44]; 516 let string = astring.gread_with::<&str>(offset, StrCtx::Length(2)); 517 match &string { 518 &Ok(_) => {}, 519 &Err(ref err) => {println!("{}", &err); panic!();} 520 } 521 assert_eq!(string.unwrap(), "E*"); 522 *offset = 0; 523 let bytes2: &[u8] = b.gread_with(offset, 2).unwrap(); 524 assert_eq!(*offset, 2); 525 assert_eq!(bytes2.len(), bytes[..].len()); 526 for i in 0..bytes2.len() { 527 assert_eq!(bytes2[i], bytes[i]) 528 } 529 } 530 531 ///////////////////////////////////////////////////////////////// 532 // end gread_with 533 ///////////////////////////////////////////////////////////////// 534 } 535