tamer: parse::trace: Extract tracing into new module
This has gotten large and was cluttering `feed_tok`. This also provides the ability to more easily expand into other types of tracing in the future. DEV-7145main
parent
8f25c9ae0a
commit
17327f1b64
|
@ -25,6 +25,7 @@ mod error;
|
|||
mod lower;
|
||||
mod parser;
|
||||
mod state;
|
||||
mod trace;
|
||||
|
||||
pub use error::ParseError;
|
||||
pub use lower::{Lower, LowerIter, ParsedObject};
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
//! High-level parsing abstraction.
|
||||
|
||||
use super::{
|
||||
trace::{self, ParserTrace},
|
||||
ParseError, ParseResult, ParseState, ParseStatus, TokenStream, Transition,
|
||||
TransitionResult,
|
||||
};
|
||||
|
@ -127,6 +128,11 @@ pub struct Parser<S: ParseState, I: TokenStream<S::Token>> {
|
|||
/// it was originally added for situations where Rust is unable to
|
||||
/// elide the move of [`Parser::state`] in [`Parser::feed_tok`].
|
||||
ctx: S::Context,
|
||||
|
||||
#[cfg(any(test, feature = "parser-trace-stderr"))]
|
||||
tracer: trace::HumanReadableTrace,
|
||||
#[cfg(not(any(test, feature = "parser-trace-stderr")))]
|
||||
tracer: trace::VoidTrace,
|
||||
}
|
||||
|
||||
impl<S: ParseState, I: TokenStream<S::Token>> Parser<S, I> {
|
||||
|
@ -146,6 +152,7 @@ impl<S: ParseState, I: TokenStream<S::Token>> Parser<S, I> {
|
|||
state: Some(state),
|
||||
last_span: UNKNOWN_SPAN,
|
||||
ctx: Default::default(),
|
||||
tracer: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,41 +257,8 @@ impl<S: ParseState, I: TokenStream<S::Token>> Parser<S, I> {
|
|||
"lookahead token is available but was not consumed",
|
||||
);
|
||||
|
||||
// Human-readable trace that will become part of a failed test
|
||||
// cases's output.
|
||||
// This describes the state prior to the transition,
|
||||
// and is left here inline since it also helps to document what
|
||||
// this method is doing.
|
||||
// This is _not_ intended to be machine-readable or stable,
|
||||
// so please do not parse it;
|
||||
// if we want a machine-readable format for e.g. creating a
|
||||
// visualization of a parse,
|
||||
// such a system can be created separately.
|
||||
//
|
||||
// Note: if one of these trace blocks does not fully output,
|
||||
// then you may have a `Display::fmt` or `Debug::fmt` panic,
|
||||
// like a `todo!` or `unimplemented!`,
|
||||
// in your `Token` or `ParseState`.
|
||||
//
|
||||
// Unfortunately Cargo can't enable this feature for us for
|
||||
// profiles;
|
||||
// see <https://github.com/rust-lang/cargo/issues/2911>.
|
||||
#[cfg(any(test, feature = "parser-trace-stderr"))]
|
||||
{
|
||||
let st = self.state.as_ref().unwrap();
|
||||
|
||||
eprint!(
|
||||
"\
|
||||
[Parser::feed_tok] (input IR: {ir})
|
||||
| ==> Parser before tok is {st}.
|
||||
| | {st:?}
|
||||
|
|
||||
| ==> {ir} tok: {tok}
|
||||
| | {tok:?}
|
||||
|\n",
|
||||
ir = S::Token::ir_name()
|
||||
);
|
||||
}
|
||||
self.tracer
|
||||
.trace_tok_begin(self.state.as_ref().unwrap(), &tok);
|
||||
|
||||
// Parse a single token and perform the requested state transition.
|
||||
//
|
||||
|
@ -301,53 +275,10 @@ impl<S: ParseState, I: TokenStream<S::Token>> Parser<S, I> {
|
|||
// Note also that this is what Dead states require transitions.
|
||||
let TransitionResult(Transition(state), data) =
|
||||
self.state.take().unwrap().parse_token(tok, &mut self.ctx);
|
||||
|
||||
self.tracer.trace_tok_end(&state, &data);
|
||||
self.state.replace(state);
|
||||
|
||||
// Remainder of the trace after the transition.
|
||||
#[cfg(any(test, feature = "parser-trace-stderr"))]
|
||||
{
|
||||
let newst = self.state.as_ref().unwrap();
|
||||
|
||||
eprint!(
|
||||
"\
|
||||
| ==> Parser after tok is {newst}.
|
||||
| | {newst:?}
|
||||
| | Lookahead: {la:?}\n",
|
||||
la = data.lookahead_ref(),
|
||||
);
|
||||
|
||||
if let Some(obj) = data.object_ref() {
|
||||
// Note that `Object` does not implement `Display`,
|
||||
// but you'll see a `Display` representation if the object
|
||||
// is passed to another `Parser` as a `Token`.
|
||||
eprint!(
|
||||
"\
|
||||
|
|
||||
| ==> Yielded object:
|
||||
| | {obj:?}\n",
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(err) = data.err_ref() {
|
||||
eprint!(
|
||||
"\
|
||||
|
|
||||
| ==> !!! error: {err}.
|
||||
| | {err:?}\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parser-trace-stderr")]
|
||||
#[allow(unused_variables)]
|
||||
let cfg = "feature = \"parser-trace-stderr\"";
|
||||
#[cfg(test)] // takes precedence if both are set
|
||||
let cfg = "test";
|
||||
eprint!(
|
||||
"note: this trace was output as a debugging aid \
|
||||
because `cfg({cfg})`.\n\n",
|
||||
);
|
||||
}
|
||||
|
||||
use ParseStatus::{Incomplete, Object};
|
||||
match data {
|
||||
// Nothing handled this dead state,
|
||||
|
@ -489,6 +420,7 @@ where
|
|||
state: Some(Default::default()),
|
||||
last_span: UNKNOWN_SPAN,
|
||||
ctx: Default::default(),
|
||||
tracer: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +443,7 @@ where
|
|||
state: Some(Default::default()),
|
||||
last_span: UNKNOWN_SPAN,
|
||||
ctx,
|
||||
tracer: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,6 @@ pub(in super::super) enum TransitionData<S: ParseState> {
|
|||
Dead(Lookahead<S::Token>),
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "parser-trace-stderr"))]
|
||||
impl<S: ParseState> TransitionData<S> {
|
||||
/// Reference to the token of lookahead,
|
||||
/// if any.
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
// Parser tracing
|
||||
//
|
||||
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
|
||||
//
|
||||
// This file is part of TAME.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tracing for parsing operations.
|
||||
//!
|
||||
//! This provides human-readable traces on standard error any time a token
|
||||
//! is fed to the parser.
|
||||
//! These traces are provided automatically when `cfg(test)`,
|
||||
//! which means that they are automatically included in the output of any
|
||||
//! test failure.
|
||||
//!
|
||||
//! Outside of tests,
|
||||
//! this can be enabled at configuration-time using the
|
||||
//! `parser-trace-stderr` feature flag
|
||||
//! (`./configure FEATURES=parser-trace-stderr`).
|
||||
//!
|
||||
//! _These traces are not meant to be machine-readable!_
|
||||
//! There may be other useful tracing formats in the future,
|
||||
//! including OpenTelemetry and the DOT graph description language.
|
||||
//! Do not try to use the human-readable traces in that way since the format
|
||||
//! is subject to change without notice.
|
||||
|
||||
use super::{state::TransitionData, ParseState, Token};
|
||||
|
||||
pub(super) trait ParserTrace: Default {
|
||||
/// Output the upper portion of a token trace.
|
||||
///
|
||||
/// This begins the trace with information about the current
|
||||
/// [`ParseState`] and the token that was received.
|
||||
/// Post-transition tracing is handled by [`Self::trace_tok_end`].
|
||||
///
|
||||
/// There is no means to return an error and a failure to output the
|
||||
/// trace should not interrupt processing.
|
||||
fn trace_tok_begin<S: ParseState>(&mut self, st_orig: &S, tok: &S::Token);
|
||||
|
||||
/// Output the lower portion of a token trace.
|
||||
///
|
||||
/// This ends the trace with information about the transition and the
|
||||
/// resulting [`ParseState`].
|
||||
///
|
||||
/// There is no means to return an error and a failure to output the
|
||||
/// trace should not interrupt processing.
|
||||
fn trace_tok_end<S: ParseState>(
|
||||
&mut self,
|
||||
st_new: &S,
|
||||
data: &TransitionData<S>,
|
||||
);
|
||||
}
|
||||
|
||||
/// Perform no tracing.
|
||||
///
|
||||
/// This should be used by default for non-test builds,
|
||||
/// since tracing can incur a significant performance cost.
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct VoidTrace;
|
||||
|
||||
impl ParserTrace for VoidTrace {
|
||||
fn trace_tok_begin<S: ParseState>(
|
||||
&mut self,
|
||||
_st_orig: &S,
|
||||
_tok: &S::Token,
|
||||
) {
|
||||
// Do nothing at all.
|
||||
}
|
||||
|
||||
fn trace_tok_end<S: ParseState>(
|
||||
&mut self,
|
||||
_st_new: &S,
|
||||
_data: &TransitionData<S>,
|
||||
) {
|
||||
// Do nothing at all.
|
||||
}
|
||||
}
|
||||
|
||||
/// Human-readable [`ParserTrace`].
|
||||
///
|
||||
///
|
||||
/// Note: if one of these trace blocks does not fully output,
|
||||
/// then you may have a `Display::fmt` or `Debug::fmt` panic---like
|
||||
/// a `todo!` or `unimplemented!`---in
|
||||
/// your `Token` or `ParseState`.
|
||||
///
|
||||
/// See [module-level](super) documentation for more information.
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct HumanReadableTrace;
|
||||
|
||||
impl ParserTrace for HumanReadableTrace {
|
||||
fn trace_tok_begin<S: ParseState>(&mut self, st_orig: &S, tok: &S::Token) {
|
||||
eprint!(
|
||||
"\
|
||||
[Parser::feed_tok] (input IR: {ir})
|
||||
| ==> Parser before tok is {st_orig}.
|
||||
| | {st_orig:?}
|
||||
|
|
||||
| ==> {ir} tok: {tok}
|
||||
| | {tok:?}
|
||||
|\n",
|
||||
ir = S::Token::ir_name()
|
||||
);
|
||||
}
|
||||
|
||||
fn trace_tok_end<S: ParseState>(
|
||||
&mut self,
|
||||
st_new: &S,
|
||||
data: &TransitionData<S>,
|
||||
) {
|
||||
eprint!(
|
||||
"\
|
||||
| ==> Parser after tok is {st_new}.
|
||||
| | {st_new:?}
|
||||
| | Lookahead: {la:?}\n",
|
||||
la = data.lookahead_ref(),
|
||||
);
|
||||
|
||||
if let Some(obj) = data.object_ref() {
|
||||
// Note that `Object` does not implement `Display`,
|
||||
// but you'll see a `Display` representation if the object
|
||||
// is passed to another `Parser` as a `Token`.
|
||||
eprint!(
|
||||
"\
|
||||
|
|
||||
| ==> Yielded object:
|
||||
| | {obj:?}\n",
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(err) = data.err_ref() {
|
||||
eprint!(
|
||||
"\
|
||||
|
|
||||
| ==> !!! error: {err}.
|
||||
| | {err:?}\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let cfg = ""; // so that this compiles without matching cfg
|
||||
#[cfg(feature = "parser-trace-stderr")]
|
||||
#[allow(unused_variables)]
|
||||
let cfg = "feature = \"parser-trace-stderr\"";
|
||||
#[cfg(test)] // takes precedence if both are set
|
||||
let cfg = "test";
|
||||
eprint!(
|
||||
"= note: this trace was output as a debugging aid \
|
||||
because `cfg({cfg})`.\n\n",
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue