1 #![allow(dead_code)] 2 3 use std::fmt::Debug; 4 5 const DEBUG_ENABLED: bool = false; 6 7 macro_rules! debug { 8 ($($args:expr),* $(,)*) => { 9 if DEBUG_ENABLED { 10 eprintln!($($args),*); 11 } 12 } 13 } 14 15 pub trait ParserDefinition: Sized { 16 /// Represents a location in the input text. If you are using the 17 /// default tokenizer, this will be a `usize`. 18 type Location: Clone + Debug; 19 20 /// Represents a "user error" -- this can get produced by 21 /// `reduce()` if the grammar includes `=>?` actions. 22 type Error; 23 24 /// The type emitted by the user's tokenizer (excluding the 25 /// location information). 26 type Token: Clone + Debug; 27 28 /// We assign a unique index to each token in the grammar, which 29 /// we call its *index*. When we pull in a new `Token` from the 30 /// input, we then match against it to determine its index. Note 31 /// that the actual `Token` is retained too, as it may carry 32 /// additional information (e.g., an `ID` terminal often has a 33 /// string value associated with it; this is not important to the 34 /// parser, but the semantic analyzer will want it). 35 type TokenIndex: Copy + Clone + Debug; 36 37 /// The type representing things on the LALRPOP stack. Represents 38 /// the union of terminals and nonterminals. 39 type Symbol; 40 41 /// Type produced by reducing the start symbol. 42 type Success; 43 44 /// Identifies a state. Typically an i8, i16, or i32 (depending on 45 /// how many states you have). 46 type StateIndex: Copy + Clone + Debug; 47 48 /// Identifies an action. 49 type Action: ParserAction<Self>; 50 51 /// Identifies a reduction. 52 type ReduceIndex: Copy + Clone + Debug; 53 54 /// Identifies a nonterminal. 55 type NonterminalIndex: Copy + Clone + Debug; 56 57 /// Returns a location representing the "start of the input". start_location(&self) -> Self::Location58 fn start_location(&self) -> Self::Location; 59 60 /// Returns the initial state. start_state(&self) -> Self::StateIndex61 fn start_state(&self) -> Self::StateIndex; 62 63 /// Converts the user's tokens into an internal index; this index 64 /// is then used to index into actions and the like. When using an 65 /// internal tokenizer, these indices are directly produced. When 66 /// using an **external** tokenier, however, this function matches 67 /// against the patterns given by the user: it is fallible 68 /// therefore as these patterns may not be exhaustive. If a token 69 /// value is found that doesn't match any of the patterns the user 70 /// supplied, then this function returns `None`, which is 71 /// translated into a parse error by LALRPOP ("unrecognized 72 /// token"). token_to_index(&self, token: &Self::Token) -> Option<Self::TokenIndex>73 fn token_to_index(&self, token: &Self::Token) -> Option<Self::TokenIndex>; 74 75 /// Given the top-most state and the pending terminal, returns an 76 /// action. This can be either SHIFT(state), REDUCE(action), or 77 /// ERROR. action(&self, state: Self::StateIndex, token_index: Self::TokenIndex) -> Self::Action78 fn action(&self, state: Self::StateIndex, token_index: Self::TokenIndex) -> Self::Action; 79 80 /// Returns the action to take if an error occurs in the given 81 /// state. This function is the same as the ordinary `action`, 82 /// except that it applies not to the user's terminals but to the 83 /// "special terminal" `!`. error_action(&self, state: Self::StateIndex) -> Self::Action84 fn error_action(&self, state: Self::StateIndex) -> Self::Action; 85 86 /// Action to take if EOF occurs in the given state. This function 87 /// is the same as the ordinary `action`, except that it applies 88 /// not to the user's terminals but to the "special terminal" `$`. eof_action(&self, state: Self::StateIndex) -> Self::Action89 fn eof_action(&self, state: Self::StateIndex) -> Self::Action; 90 91 /// If we reduce to a nonterminal in the given state, what state 92 /// do we go to? This is infallible due to the nature of LR(1) 93 /// grammars. goto(&self, state: Self::StateIndex, nt: Self::NonterminalIndex) -> Self::StateIndex94 fn goto(&self, state: Self::StateIndex, nt: Self::NonterminalIndex) -> Self::StateIndex; 95 96 /// "Upcast" a terminal into a symbol so we can push it onto the 97 /// parser stack. token_to_symbol(&self, token_index: Self::TokenIndex, token: Self::Token) -> Self::Symbol98 fn token_to_symbol(&self, token_index: Self::TokenIndex, token: Self::Token) -> Self::Symbol; 99 100 /// Returns the expected tokens in a given state. This is used for 101 /// error reporting. expected_tokens(&self, state: Self::StateIndex) -> Vec<String>102 fn expected_tokens(&self, state: Self::StateIndex) -> Vec<String>; 103 104 /// True if this grammar supports error recovery. uses_error_recovery(&self) -> bool105 fn uses_error_recovery(&self) -> bool; 106 107 /// Given error information, creates an error recovery symbol that 108 /// we push onto the stack (and supply to user actions). error_recovery_symbol(&self, recovery: ErrorRecovery<Self>) -> Self::Symbol109 fn error_recovery_symbol(&self, recovery: ErrorRecovery<Self>) -> Self::Symbol; 110 111 /// Execute a reduction in the given state: that is, execute user 112 /// code. The start location indicates the "starting point" of the 113 /// current lookahead that is triggering the reduction (it is 114 /// `None` for EOF). 115 /// 116 /// The `states` and `symbols` vectors represent the internal 117 /// state machine vectors; they are given to `reduce` so that it 118 /// can pop off states that no longer apply (and consume their 119 /// symbols). At the end, it should also push the new state and 120 /// symbol produced. 121 /// 122 /// Returns a `Some` if we reduced the start state and hence 123 /// parsing is complete, or if we encountered an irrecoverable 124 /// error. 125 /// 126 /// FIXME. It would be nice to not have so much logic live in 127 /// reduce. It should just be given an iterator of popped symbols 128 /// and return the newly produced symbol (or error). We can use 129 /// `simulate_reduce` and our own information to drive the rest, 130 /// right? This would also allow us -- I think -- to extend error 131 /// recovery to cover user-produced errors. reduce( &mut self, reduce_index: Self::ReduceIndex, start_location: Option<&Self::Location>, states: &mut Vec<Self::StateIndex>, symbols: &mut Vec<SymbolTriple<Self>>, ) -> Option<ParseResult<Self>>132 fn reduce( 133 &mut self, 134 reduce_index: Self::ReduceIndex, 135 start_location: Option<&Self::Location>, 136 states: &mut Vec<Self::StateIndex>, 137 symbols: &mut Vec<SymbolTriple<Self>>, 138 ) -> Option<ParseResult<Self>>; 139 140 /// Returns information about how many states will be popped 141 /// during a reduction, and what nonterminal would be produced as 142 /// a result. simulate_reduce(&self, action: Self::ReduceIndex) -> SimulatedReduce<Self>143 fn simulate_reduce(&self, action: Self::ReduceIndex) -> SimulatedReduce<Self>; 144 } 145 146 pub trait ParserAction<D: ParserDefinition>: Copy + Clone + Debug { as_shift(self) -> Option<D::StateIndex>147 fn as_shift(self) -> Option<D::StateIndex>; as_reduce(self) -> Option<D::ReduceIndex>148 fn as_reduce(self) -> Option<D::ReduceIndex>; is_shift(self) -> bool149 fn is_shift(self) -> bool; is_reduce(self) -> bool150 fn is_reduce(self) -> bool; is_error(self) -> bool151 fn is_error(self) -> bool; 152 } 153 154 pub enum SimulatedReduce<D: ParserDefinition> { 155 Reduce { 156 states_to_pop: usize, 157 nonterminal_produced: D::NonterminalIndex, 158 }, 159 160 // This reduce is the "start" fn, so the parse is done. 161 Accept, 162 } 163 164 // These aliases are an elaborate hack to get around 165 // the warnings when you define a type alias like `type Foo<D: Trait>` 166 #[doc(hidden)] 167 pub type Location<D> = <D as ParserDefinition>::Location; 168 #[doc(hidden)] 169 pub type Token<D> = <D as ParserDefinition>::Token; 170 #[doc(hidden)] 171 pub type Error<D> = <D as ParserDefinition>::Error; 172 #[doc(hidden)] 173 pub type Success<D> = <D as ParserDefinition>::Success; 174 #[doc(hidden)] 175 pub type Symbol<D> = <D as ParserDefinition>::Symbol; 176 177 pub type ParseError<D> = ::ParseError<Location<D>, Token<D>, Error<D>>; 178 pub type ParseResult<D> = Result<Success<D>, ParseError<D>>; 179 pub type TokenTriple<D> = (Location<D>, Token<D>, Location<D>); 180 pub type SymbolTriple<D> = (Location<D>, Symbol<D>, Location<D>); 181 pub type ErrorRecovery<D> = ::ErrorRecovery<Location<D>, Token<D>, Error<D>>; 182 183 pub struct Parser<D, I> 184 where 185 D: ParserDefinition, 186 I: Iterator<Item = Result<TokenTriple<D>, ParseError<D>>>, 187 { 188 definition: D, 189 tokens: I, 190 states: Vec<D::StateIndex>, 191 symbols: Vec<SymbolTriple<D>>, 192 last_location: D::Location, 193 } 194 195 enum NextToken<D: ParserDefinition> { 196 FoundToken(TokenTriple<D>, D::TokenIndex), 197 EOF, 198 Done(ParseResult<D>), 199 } 200 201 impl<D, I> Parser<D, I> 202 where 203 D: ParserDefinition, 204 I: Iterator<Item = Result<TokenTriple<D>, ParseError<D>>>, 205 { drive(definition: D, tokens: I) -> ParseResult<D>206 pub fn drive(definition: D, tokens: I) -> ParseResult<D> { 207 let last_location = definition.start_location(); 208 let start_state = definition.start_state(); 209 Parser { 210 definition, 211 tokens, 212 states: vec![start_state], 213 symbols: vec![], 214 last_location, 215 }.parse() 216 } 217 top_state(&self) -> D::StateIndex218 fn top_state(&self) -> D::StateIndex { 219 *self.states.last().unwrap() 220 } 221 parse(&mut self) -> ParseResult<D>222 fn parse(&mut self) -> ParseResult<D> { 223 // Outer loop: each time we continue around this loop, we 224 // shift a new token from the input. We break from the loop 225 // when the end of the input is reached (we return early if an 226 // error occurs). 227 'shift: loop { 228 let (mut lookahead, mut token_index) = match self.next_token() { 229 NextToken::FoundToken(l, i) => (l, i), 230 NextToken::EOF => return self.parse_eof(), 231 NextToken::Done(e) => return e, 232 }; 233 234 debug!("+ SHIFT: {:?}", lookahead); 235 236 debug!("\\ token_index: {:?}", token_index); 237 238 'inner: loop { 239 let top_state = self.top_state(); 240 let action = self.definition.action(top_state, token_index); 241 debug!("\\ action: {:?}", action); 242 243 if let Some(target_state) = action.as_shift() { 244 debug!("\\ shift to: {:?}", target_state); 245 246 // Shift and transition to state `action - 1` 247 let symbol = self.definition.token_to_symbol(token_index, lookahead.1); 248 self.states.push(target_state); 249 self.symbols.push((lookahead.0, symbol, lookahead.2)); 250 continue 'shift; 251 } else if let Some(reduce_index) = action.as_reduce() { 252 debug!("\\ reduce to: {:?}", reduce_index); 253 254 if let Some(r) = self.definition.reduce( 255 reduce_index, 256 Some(&lookahead.0), 257 &mut self.states, 258 &mut self.symbols, 259 ) { 260 return match r { 261 // we reached eof, but still have lookahead 262 Ok(_) => Err(::ParseError::ExtraToken { token: lookahead }), 263 Err(e) => Err(e), 264 }; 265 } 266 } else { 267 debug!("\\ error -- initiating error recovery!"); 268 269 match self.error_recovery(Some(lookahead), Some(token_index)) { 270 NextToken::FoundToken(l, i) => { 271 lookahead = l; 272 token_index = i; 273 continue 'inner; 274 } 275 NextToken::EOF => return self.parse_eof(), 276 NextToken::Done(e) => return e, 277 } 278 } 279 } 280 } 281 } 282 283 /// Invoked when we have no more tokens to consume. parse_eof(&mut self) -> ParseResult<D>284 fn parse_eof(&mut self) -> ParseResult<D> { 285 loop { 286 let top_state = self.top_state(); 287 let action = self.definition.eof_action(top_state); 288 if let Some(reduce_index) = action.as_reduce() { 289 if let Some(result) = 290 self.definition 291 .reduce(reduce_index, None, &mut self.states, &mut self.symbols) 292 { 293 return result; 294 } 295 } else { 296 match self.error_recovery(None, None) { 297 NextToken::FoundToken(..) => panic!("cannot find token at EOF"), 298 NextToken::Done(e) => return e, 299 NextToken::EOF => continue, 300 } 301 } 302 } 303 } 304 error_recovery( &mut self, mut opt_lookahead: Option<TokenTriple<D>>, mut opt_token_index: Option<D::TokenIndex>, ) -> NextToken<D>305 fn error_recovery( 306 &mut self, 307 mut opt_lookahead: Option<TokenTriple<D>>, 308 mut opt_token_index: Option<D::TokenIndex>, 309 ) -> NextToken<D> { 310 debug!( 311 "\\+ error_recovery(opt_lookahead={:?}, opt_token_index={:?})", 312 opt_lookahead, 313 opt_token_index, 314 ); 315 316 if !self.definition.uses_error_recovery() { 317 debug!("\\ error -- no error recovery!"); 318 319 return NextToken::Done(Err(self.unrecognized_token_error( 320 opt_lookahead, 321 self.top_state(), 322 ))); 323 } 324 325 let error = self.unrecognized_token_error(opt_lookahead.clone(), self.top_state()); 326 327 let mut dropped_tokens = vec![]; 328 329 // We are going to insert ERROR into the lookahead. So, first, 330 // perform all reductions from current state triggered by having 331 // ERROR in the lookahead. 332 loop { 333 let state = self.top_state(); 334 let action = self.definition.error_action(state); 335 if let Some(reduce_index) = action.as_reduce() { 336 debug!("\\\\ reducing: {:?}", reduce_index); 337 338 if let Some(result) = 339 self.reduce(reduce_index, opt_lookahead.as_ref().map(|l| &l.0)) 340 { 341 debug!("\\\\ reduced to a result"); 342 343 return NextToken::Done(result); 344 } 345 } else { 346 break; 347 } 348 } 349 350 // Now try to find the recovery state. 351 let states_len = self.states.len(); 352 let top = 'find_state: loop { 353 // Go backwards through the states... 354 debug!( 355 "\\\\+ error_recovery: find_state loop, {:?} states = {:?}", 356 self.states.len(), 357 self.states, 358 ); 359 360 for top in (0..states_len).rev() { 361 let state = self.states[top]; 362 debug!("\\\\\\ top = {:?}, state = {:?}", top, state); 363 364 // ...fetch action for error token... 365 let action = self.definition.error_action(state); 366 debug!("\\\\\\ action = {:?}", action); 367 if let Some(error_state) = action.as_shift() { 368 // If action is a shift that takes us into `error_state`, 369 // and `error_state` can accept this lookahead, we are done. 370 if self.accepts(error_state, &self.states[..top + 1], opt_token_index) { 371 debug!("\\\\\\ accepted!"); 372 break 'find_state top; 373 } 374 } else { 375 // ...else, if action is error or reduce, go to next state. 376 continue; 377 } 378 } 379 380 // Otherwise, if we couldn't find a state that would -- 381 // after shifting the error token -- accept the lookahead, 382 // then drop the lookahead and advance to next token in 383 // the input. 384 match opt_lookahead.take() { 385 // If the lookahead is EOF, we can't drop any more 386 // tokens, abort error recovery and just report the 387 // original error (it might be nice if we would 388 // propagate back the dropped tokens, though). 389 None => { 390 debug!("\\\\\\ no more lookahead, report error"); 391 return NextToken::Done(Err(error)); 392 } 393 394 // Else, drop the current token and shift to the 395 // next. If there is a next token, we will `continue` 396 // to the start of the `'find_state` loop. 397 Some(lookahead) => { 398 debug!("\\\\\\ dropping lookahead token"); 399 400 dropped_tokens.push(lookahead); 401 match self.next_token() { 402 NextToken::FoundToken(next_lookahead, next_token_index) => { 403 opt_lookahead = Some(next_lookahead); 404 opt_token_index = Some(next_token_index); 405 } 406 NextToken::EOF => { 407 debug!("\\\\\\ reached EOF"); 408 opt_lookahead = None; 409 opt_token_index = None; 410 } 411 NextToken::Done(e) => { 412 debug!("\\\\\\ no more tokens"); 413 return NextToken::Done(e); 414 } 415 } 416 } 417 } 418 }; 419 420 // If we get here, we are ready to push the error recovery state. 421 422 // We have to compute the span for the error recovery 423 // token. We do this first, before we pop any symbols off the 424 // stack. There are several possibilities, in order of 425 // preference. 426 // 427 // For the **start** of the message, we prefer to use the start of any 428 // popped states. This represents parts of the input we had consumed but 429 // had to roll back and ignore. 430 // 431 // Example: 432 // 433 // a + (b + /) 434 // ^ start point is here, since this `+` will be popped off 435 // 436 // If there are no popped states, but there *are* dropped tokens, we can use 437 // the start of those. 438 // 439 // Example: 440 // 441 // a + (b + c e) 442 // ^ start point would be here 443 // 444 // Finally, if there are no popped states *nor* dropped tokens, we can use 445 // the end of the top-most state. 446 447 let start = if let Some(popped_sym) = self.symbols.get(top) { 448 popped_sym.0.clone() 449 } else if let Some(dropped_token) = dropped_tokens.first() { 450 dropped_token.0.clone() 451 } else if top > 0 { 452 self.symbols[top - 1].2.clone() 453 } else { 454 self.definition.start_location() 455 }; 456 457 // For the end span, here are the possibilities: 458 // 459 // We prefer to use the end of the last dropped token. 460 // 461 // Examples: 462 // 463 // a + (b + /) 464 // --- 465 // a + (b c) 466 // - 467 // 468 // But, if there are no dropped tokens, we will use the end of the popped states, 469 // if any: 470 // 471 // a + / 472 // - 473 // 474 // If there are neither dropped tokens *or* popped states, 475 // then the user is simulating insertion of an operator. In 476 // this case, we prefer the start of the lookahead, but 477 // fallback to the start if we are at EOF. 478 // 479 // Examples: 480 // 481 // a + (b c) 482 // - 483 484 let end = if let Some(dropped_token) = dropped_tokens.last() { 485 dropped_token.2.clone() 486 } else if states_len - 1 > top { 487 self.symbols.last().unwrap().2.clone() 488 } else if let Some(lookahead) = opt_lookahead.as_ref() { 489 lookahead.0.clone() 490 } else { 491 start.clone() 492 }; 493 494 self.states.truncate(top + 1); 495 self.symbols.truncate(top); 496 497 let recover_state = self.states[top]; 498 let error_action = self.definition.error_action(recover_state); 499 let error_state = error_action.as_shift().unwrap(); 500 self.states.push(error_state); 501 let recovery = self.definition.error_recovery_symbol(::ErrorRecovery { 502 error: error, 503 dropped_tokens: dropped_tokens, 504 }); 505 self.symbols.push((start, recovery, end)); 506 507 match (opt_lookahead, opt_token_index) { 508 (Some(l), Some(i)) => NextToken::FoundToken(l, i), 509 (None, None) => NextToken::EOF, 510 (l, i) => panic!("lookahead and token_index mismatched: {:?}, {:?}", l, i), 511 } 512 } 513 514 /// The `accepts` function has the job of figuring out whether the 515 /// given error state would "accept" the given lookahead. We 516 /// basically trace through the LR automaton looking for one of 517 /// two outcomes: 518 /// 519 /// - the lookahead is eventually shifted 520 /// - we reduce to the end state successfully (in the case of EOF). 521 /// 522 /// If we used the pure LR(1) algorithm, we wouldn't need this 523 /// function, because we would be guaranteed to error immediately 524 /// (and not after some number of reductions). But with an LALR 525 /// (or Lane Table) generated automaton, it is possible to reduce 526 /// some number of times before encountering an error. Failing to 527 /// take this into account can lead error recovery into an 528 /// infinite loop (see the `error_recovery_lalr_loop` test) or 529 /// produce crappy results (see `error_recovery_lock_in`). accepts( &self, error_state: D::StateIndex, states: &[D::StateIndex], opt_token_index: Option<D::TokenIndex>, ) -> bool530 fn accepts( 531 &self, 532 error_state: D::StateIndex, 533 states: &[D::StateIndex], 534 opt_token_index: Option<D::TokenIndex>, 535 ) -> bool { 536 debug!( 537 "\\\\\\+ accepts(error_state={:?}, states={:?}, opt_token_index={:?})", 538 error_state, 539 states, 540 opt_token_index, 541 ); 542 543 let mut states = states.to_vec(); 544 states.push(error_state); 545 loop { 546 let mut states_len = states.len(); 547 let top = states[states_len - 1]; 548 let action = match opt_token_index { 549 None => self.definition.eof_action(top), 550 Some(i) => self.definition.action(top, i), 551 }; 552 553 // If we encounter an error action, we do **not** accept. 554 if action.is_error() { 555 debug!("\\\\\\\\ accepts: error"); 556 return false; 557 } 558 559 // If we encounter a reduce action, we need to simulate its 560 // effect on the state stack. 561 if let Some(reduce_action) = action.as_reduce() { 562 match self.definition.simulate_reduce(reduce_action) { 563 SimulatedReduce::Reduce { 564 states_to_pop, 565 nonterminal_produced, 566 } => { 567 states_len -= states_to_pop; 568 states.truncate(states_len); 569 let top = states[states_len - 1]; 570 let next_state = self.definition.goto(top, nonterminal_produced); 571 states.push(next_state); 572 } 573 574 SimulatedReduce::Accept => { 575 debug!("\\\\\\\\ accepts: reduce accepts!"); 576 return true; 577 } 578 } 579 } else { 580 // If we encounter a shift action, we DO accept. 581 debug!("\\\\\\\\ accepts: shift accepts!"); 582 assert!(action.is_shift()); 583 return true; 584 } 585 } 586 } 587 reduce( &mut self, action: D::ReduceIndex, lookahead_start: Option<&D::Location>, ) -> Option<ParseResult<D>>588 fn reduce( 589 &mut self, 590 action: D::ReduceIndex, 591 lookahead_start: Option<&D::Location>, 592 ) -> Option<ParseResult<D>> { 593 self.definition 594 .reduce(action, lookahead_start, &mut self.states, &mut self.symbols) 595 } 596 unrecognized_token_error( &self, token: Option<TokenTriple<D>>, top_state: D::StateIndex, ) -> ParseError<D>597 fn unrecognized_token_error( 598 &self, 599 token: Option<TokenTriple<D>>, 600 top_state: D::StateIndex, 601 ) -> ParseError<D> { 602 ::ParseError::UnrecognizedToken { 603 token: token, 604 expected: self.definition.expected_tokens(top_state), 605 } 606 } 607 608 /// Consume the next token from the input and classify it into a 609 /// token index. Classification can fail with an error. If there 610 /// are no more tokens, signal EOF. next_token(&mut self) -> NextToken<D>611 fn next_token(&mut self) -> NextToken<D> { 612 let token = match self.tokens.next() { 613 Some(Ok(v)) => v, 614 Some(Err(e)) => return NextToken::Done(Err(e)), 615 None => return NextToken::EOF, 616 }; 617 618 self.last_location = token.2.clone(); 619 620 let token_index = match self.definition.token_to_index(&token.1) { 621 Some(i) => i, 622 None => { 623 return NextToken::Done(Err( 624 self.unrecognized_token_error(Some(token), self.top_state()) 625 )) 626 } 627 }; 628 629 NextToken::FoundToken(token, token_index) 630 } 631 } 632 633 /// In LALRPOP generated rules, we actually use `i32`, `i16`, or `i8` 634 /// to represent all of the various indices (we use the smallest one 635 /// that will fit). So implement `ParserAction` for each of those. 636 macro_rules! integral_indices { 637 ($t:ty) => { 638 impl<D: ParserDefinition<StateIndex = $t, ReduceIndex = $t>> ParserAction<D> for $t { 639 fn as_shift(self) -> Option<D::StateIndex> { 640 if self > 0 { 641 Some(self - 1) 642 } else { 643 None 644 } 645 } 646 647 fn as_reduce(self) -> Option<D::ReduceIndex> { 648 if self < 0 { 649 Some(-(self + 1)) 650 } else { 651 None 652 } 653 } 654 655 fn is_shift(self) -> bool { 656 self > 0 657 } 658 659 fn is_reduce(self) -> bool { 660 self < 0 661 } 662 663 fn is_error(self) -> bool { 664 self == 0 665 } 666 } 667 }; 668 } 669 670 integral_indices!(i32); 671 integral_indices!(i16); 672 integral_indices!(i8); 673