Skip to main content

NativeRealtimeParser

Struct NativeRealtimeParser 

Source
pub struct NativeRealtimeParser { /* private fields */ }
Expand description

A pseudo-incremental native parser retaining independently framed cells.

Appending to an EOF-complete cell reparses only that speculative cell. Confirmed cells above it retain their token and typed allocations. An edit inside an earlier cell reparses strictly forwards; after cell framing stabilizes, an unchanged suffix is reused in its entirety. Adjacency and cell boundaries are regenerated together for every reparsed region.

Implementations§

Source§

impl NativeRealtimeParser

Source

pub fn new(source: impl Into<String>) -> Self

Parses and retains an initial source snapshot.

Examples found in repository?
examples/live_parser.rs (line 108)
104fn main() -> io::Result<()> {
105    let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
106    let _terminal = TerminalMode::raw();
107    let mut bytes = Vec::new();
108    let mut parser = NativeRealtimeParser::new("");
109    let mut stdout = io::stdout().lock();
110
111    if interactive {
112        stdout.write_all(render(&parser, true).as_bytes())?;
113        stdout.flush()?;
114    }
115
116    for byte in io::stdin().lock().bytes() {
117        let byte = byte?;
118        match byte {
119            3 | 4 => break,
120            8 | 127 => {
121                if let Ok(source) = std::str::from_utf8(&bytes)
122                    && let Some((index, _)) = source.char_indices().next_back()
123                {
124                    bytes.truncate(index);
125                }
126            }
127            b'\r' => bytes.push(b'\n'),
128            byte => bytes.push(byte),
129        }
130        let Ok(source) = std::str::from_utf8(&bytes) else {
131            continue;
132        };
133        if let Some(appended) = source.strip_prefix(parser.source()) {
134            parser.append(appended);
135        } else if parser.source().starts_with(source) {
136            parser
137                .apply_edit(&TextEdit::deletion(source.len(), parser.source().len()))
138                .expect("backspace always removes a valid source suffix");
139        } else {
140            parser = NativeRealtimeParser::new(source);
141        }
142        if interactive {
143            stdout.write_all(render(&parser, true).as_bytes())?;
144            stdout.flush()?;
145        }
146    }
147
148    if !interactive {
149        let source = String::from_utf8(bytes)
150            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
151        debug_assert_eq!(source, parser.source());
152        stdout.write_all(render(&parser, false).as_bytes())?;
153    }
154    Ok(())
155}
Source

pub fn apply_edit( &mut self, edit: &TextEdit, ) -> Result<NativeCellState, NativeRealtimeEditError>

Applies an edit and reparses only the forward cell region it invalidates.

The operation is transactional when the edit is outside the source or crosses a UTF-8 character boundary. Invalid Macaulay2 syntax is retained as NativeCellState::Invalid rather than failing the edit operation.

§Errors

Returns NativeRealtimeEditError when edit cannot be applied to the current source buffer.

Examples found in repository?
examples/live_parser.rs (line 137)
104fn main() -> io::Result<()> {
105    let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
106    let _terminal = TerminalMode::raw();
107    let mut bytes = Vec::new();
108    let mut parser = NativeRealtimeParser::new("");
109    let mut stdout = io::stdout().lock();
110
111    if interactive {
112        stdout.write_all(render(&parser, true).as_bytes())?;
113        stdout.flush()?;
114    }
115
116    for byte in io::stdin().lock().bytes() {
117        let byte = byte?;
118        match byte {
119            3 | 4 => break,
120            8 | 127 => {
121                if let Ok(source) = std::str::from_utf8(&bytes)
122                    && let Some((index, _)) = source.char_indices().next_back()
123                {
124                    bytes.truncate(index);
125                }
126            }
127            b'\r' => bytes.push(b'\n'),
128            byte => bytes.push(byte),
129        }
130        let Ok(source) = std::str::from_utf8(&bytes) else {
131            continue;
132        };
133        if let Some(appended) = source.strip_prefix(parser.source()) {
134            parser.append(appended);
135        } else if parser.source().starts_with(source) {
136            parser
137                .apply_edit(&TextEdit::deletion(source.len(), parser.source().len()))
138                .expect("backspace always removes a valid source suffix");
139        } else {
140            parser = NativeRealtimeParser::new(source);
141        }
142        if interactive {
143            stdout.write_all(render(&parser, true).as_bytes())?;
144            stdout.flush()?;
145        }
146    }
147
148    if !interactive {
149        let source = String::from_utf8(bytes)
150            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
151        debug_assert_eq!(source, parser.source());
152        stdout.write_all(render(&parser, false).as_bytes())?;
153    }
154    Ok(())
155}
Source

pub fn append(&mut self, source: impl Into<String>) -> NativeCellState

Appends source text at the current EOF.

§Panics

Panics if the parser’s retained source length is not a valid insertion boundary, which would violate its internal UTF-8 source invariant.

Examples found in repository?
examples/live_parser.rs (line 134)
104fn main() -> io::Result<()> {
105    let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
106    let _terminal = TerminalMode::raw();
107    let mut bytes = Vec::new();
108    let mut parser = NativeRealtimeParser::new("");
109    let mut stdout = io::stdout().lock();
110
111    if interactive {
112        stdout.write_all(render(&parser, true).as_bytes())?;
113        stdout.flush()?;
114    }
115
116    for byte in io::stdin().lock().bytes() {
117        let byte = byte?;
118        match byte {
119            3 | 4 => break,
120            8 | 127 => {
121                if let Ok(source) = std::str::from_utf8(&bytes)
122                    && let Some((index, _)) = source.char_indices().next_back()
123                {
124                    bytes.truncate(index);
125                }
126            }
127            b'\r' => bytes.push(b'\n'),
128            byte => bytes.push(byte),
129        }
130        let Ok(source) = std::str::from_utf8(&bytes) else {
131            continue;
132        };
133        if let Some(appended) = source.strip_prefix(parser.source()) {
134            parser.append(appended);
135        } else if parser.source().starts_with(source) {
136            parser
137                .apply_edit(&TextEdit::deletion(source.len(), parser.source().len()))
138                .expect("backspace always removes a valid source suffix");
139        } else {
140            parser = NativeRealtimeParser::new(source);
141        }
142        if interactive {
143            stdout.write_all(render(&parser, true).as_bytes())?;
144            stdout.flush()?;
145        }
146    }
147
148    if !interactive {
149        let source = String::from_utf8(bytes)
150            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
151        debug_assert_eq!(source, parser.source());
152        stdout.write_all(render(&parser, false).as_bytes())?;
153    }
154    Ok(())
155}
Source

pub fn source(&self) -> &str

Returns the complete current source buffer.

Examples found in repository?
examples/live_parser.rs (line 57)
50fn render(parser: &NativeRealtimeParser, clear: bool) -> String {
51    let mut output = String::new();
52    if clear {
53        output.push_str("\u{1b}[2J\u{1b}[H");
54    }
55    output.push_str("m2-syn live parser  (Ctrl-C/Ctrl-D exit, Backspace edits)\n\n");
56    output.push_str("SOURCE\n");
57    output.push_str(parser.source());
58    if !parser.source().ends_with('\n') {
59        output.push('\n');
60    }
61
62    let diagnostic_count = parser
63        .cells()
64        .iter()
65        .map(|cell| cell.lexer_diagnostics().len())
66        .sum::<usize>();
67    output.push_str(&format!(
68        "\nSTATE\n{:?}; {} cell(s); {} reparsed; {} lexer diagnostic(s)\n",
69        parser.state(),
70        parser.cells().len(),
71        parser.reparsed_cells().len(),
72        diagnostic_count,
73    ));
74    for cell in parser.cells() {
75        for diagnostic in cell.lexer_diagnostics() {
76            write_diagnostic(&mut output, diagnostic);
77        }
78    }
79
80    output.push_str("\nTOKENS\n");
81    for (index, cell) in parser.cells().iter().enumerate() {
82        output.push_str(&format!(
83            "cell {} · {:?} · {:?}\n",
84            index + 1,
85            cell.boundary(),
86            cell.state()
87        ));
88        output.push_str(&format_pretty_cell_tokens(cell.tokens()));
89    }
90
91    output.push_str("\nSYNTAX\n");
92    for (index, cell) in parser.cells().iter().enumerate() {
93        output.push_str(&format!("cell {}\n", index + 1));
94        if let Some(syntax) = cell.syntax() {
95            output.push_str(&format_pretty_tree(syntax));
96        }
97        if let Some(error) = cell.parse_error() {
98            output.push_str(&format!("{error}\n"));
99        }
100    }
101    output
102}
103
104fn main() -> io::Result<()> {
105    let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
106    let _terminal = TerminalMode::raw();
107    let mut bytes = Vec::new();
108    let mut parser = NativeRealtimeParser::new("");
109    let mut stdout = io::stdout().lock();
110
111    if interactive {
112        stdout.write_all(render(&parser, true).as_bytes())?;
113        stdout.flush()?;
114    }
115
116    for byte in io::stdin().lock().bytes() {
117        let byte = byte?;
118        match byte {
119            3 | 4 => break,
120            8 | 127 => {
121                if let Ok(source) = std::str::from_utf8(&bytes)
122                    && let Some((index, _)) = source.char_indices().next_back()
123                {
124                    bytes.truncate(index);
125                }
126            }
127            b'\r' => bytes.push(b'\n'),
128            byte => bytes.push(byte),
129        }
130        let Ok(source) = std::str::from_utf8(&bytes) else {
131            continue;
132        };
133        if let Some(appended) = source.strip_prefix(parser.source()) {
134            parser.append(appended);
135        } else if parser.source().starts_with(source) {
136            parser
137                .apply_edit(&TextEdit::deletion(source.len(), parser.source().len()))
138                .expect("backspace always removes a valid source suffix");
139        } else {
140            parser = NativeRealtimeParser::new(source);
141        }
142        if interactive {
143            stdout.write_all(render(&parser, true).as_bytes())?;
144            stdout.flush()?;
145        }
146    }
147
148    if !interactive {
149        let source = String::from_utf8(bytes)
150            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
151        debug_assert_eq!(source, parser.source());
152        stdout.write_all(render(&parser, false).as_bytes())?;
153    }
154    Ok(())
155}
Source

pub fn cells(&self) -> &[NativeRealtimeCell]

Returns every retained complete, incomplete, or invalid cell.

Examples found in repository?
examples/live_parser.rs (line 63)
50fn render(parser: &NativeRealtimeParser, clear: bool) -> String {
51    let mut output = String::new();
52    if clear {
53        output.push_str("\u{1b}[2J\u{1b}[H");
54    }
55    output.push_str("m2-syn live parser  (Ctrl-C/Ctrl-D exit, Backspace edits)\n\n");
56    output.push_str("SOURCE\n");
57    output.push_str(parser.source());
58    if !parser.source().ends_with('\n') {
59        output.push('\n');
60    }
61
62    let diagnostic_count = parser
63        .cells()
64        .iter()
65        .map(|cell| cell.lexer_diagnostics().len())
66        .sum::<usize>();
67    output.push_str(&format!(
68        "\nSTATE\n{:?}; {} cell(s); {} reparsed; {} lexer diagnostic(s)\n",
69        parser.state(),
70        parser.cells().len(),
71        parser.reparsed_cells().len(),
72        diagnostic_count,
73    ));
74    for cell in parser.cells() {
75        for diagnostic in cell.lexer_diagnostics() {
76            write_diagnostic(&mut output, diagnostic);
77        }
78    }
79
80    output.push_str("\nTOKENS\n");
81    for (index, cell) in parser.cells().iter().enumerate() {
82        output.push_str(&format!(
83            "cell {} · {:?} · {:?}\n",
84            index + 1,
85            cell.boundary(),
86            cell.state()
87        ));
88        output.push_str(&format_pretty_cell_tokens(cell.tokens()));
89    }
90
91    output.push_str("\nSYNTAX\n");
92    for (index, cell) in parser.cells().iter().enumerate() {
93        output.push_str(&format!("cell {}\n", index + 1));
94        if let Some(syntax) = cell.syntax() {
95            output.push_str(&format_pretty_tree(syntax));
96        }
97        if let Some(error) = cell.parse_error() {
98            output.push_str(&format!("{error}\n"));
99        }
100    }
101    output
102}
Source

pub fn reparsed_cells(&self) -> Range<usize> ⓘ

Returns the range of newly analyzed cells after the most recent edit.

Cells outside this range were retained from the preceding snapshot.

Examples found in repository?
examples/live_parser.rs (line 71)
50fn render(parser: &NativeRealtimeParser, clear: bool) -> String {
51    let mut output = String::new();
52    if clear {
53        output.push_str("\u{1b}[2J\u{1b}[H");
54    }
55    output.push_str("m2-syn live parser  (Ctrl-C/Ctrl-D exit, Backspace edits)\n\n");
56    output.push_str("SOURCE\n");
57    output.push_str(parser.source());
58    if !parser.source().ends_with('\n') {
59        output.push('\n');
60    }
61
62    let diagnostic_count = parser
63        .cells()
64        .iter()
65        .map(|cell| cell.lexer_diagnostics().len())
66        .sum::<usize>();
67    output.push_str(&format!(
68        "\nSTATE\n{:?}; {} cell(s); {} reparsed; {} lexer diagnostic(s)\n",
69        parser.state(),
70        parser.cells().len(),
71        parser.reparsed_cells().len(),
72        diagnostic_count,
73    ));
74    for cell in parser.cells() {
75        for diagnostic in cell.lexer_diagnostics() {
76            write_diagnostic(&mut output, diagnostic);
77        }
78    }
79
80    output.push_str("\nTOKENS\n");
81    for (index, cell) in parser.cells().iter().enumerate() {
82        output.push_str(&format!(
83            "cell {} · {:?} · {:?}\n",
84            index + 1,
85            cell.boundary(),
86            cell.state()
87        ));
88        output.push_str(&format_pretty_cell_tokens(cell.tokens()));
89    }
90
91    output.push_str("\nSYNTAX\n");
92    for (index, cell) in parser.cells().iter().enumerate() {
93        output.push_str(&format!("cell {}\n", index + 1));
94        if let Some(syntax) = cell.syntax() {
95            output.push_str(&format_pretty_tree(syntax));
96        }
97        if let Some(error) = cell.parse_error() {
98            output.push_str(&format!("{error}\n"));
99        }
100    }
101    output
102}
Source

pub fn state(&self) -> NativeCellState

Returns the aggregate state of the current source snapshot.

Examples found in repository?
examples/live_parser.rs (line 69)
50fn render(parser: &NativeRealtimeParser, clear: bool) -> String {
51    let mut output = String::new();
52    if clear {
53        output.push_str("\u{1b}[2J\u{1b}[H");
54    }
55    output.push_str("m2-syn live parser  (Ctrl-C/Ctrl-D exit, Backspace edits)\n\n");
56    output.push_str("SOURCE\n");
57    output.push_str(parser.source());
58    if !parser.source().ends_with('\n') {
59        output.push('\n');
60    }
61
62    let diagnostic_count = parser
63        .cells()
64        .iter()
65        .map(|cell| cell.lexer_diagnostics().len())
66        .sum::<usize>();
67    output.push_str(&format!(
68        "\nSTATE\n{:?}; {} cell(s); {} reparsed; {} lexer diagnostic(s)\n",
69        parser.state(),
70        parser.cells().len(),
71        parser.reparsed_cells().len(),
72        diagnostic_count,
73    ));
74    for cell in parser.cells() {
75        for diagnostic in cell.lexer_diagnostics() {
76            write_diagnostic(&mut output, diagnostic);
77        }
78    }
79
80    output.push_str("\nTOKENS\n");
81    for (index, cell) in parser.cells().iter().enumerate() {
82        output.push_str(&format!(
83            "cell {} · {:?} · {:?}\n",
84            index + 1,
85            cell.boundary(),
86            cell.state()
87        ));
88        output.push_str(&format_pretty_cell_tokens(cell.tokens()));
89    }
90
91    output.push_str("\nSYNTAX\n");
92    for (index, cell) in parser.cells().iter().enumerate() {
93        output.push_str(&format!("cell {}\n", index + 1));
94        if let Some(syntax) = cell.syntax() {
95            output.push_str(&format_pretty_tree(syntax));
96        }
97        if let Some(error) = cell.parse_error() {
98            output.push_str(&format!("{error}\n"));
99        }
100    }
101    output
102}
Source

pub fn cell_stream(&self) -> CellStream

Projects the retained cells as the parser-facing tokenized stream.

The projection includes an incomplete or invalid tail so a parser or macro can inspect the exact current tokenization.

Trait Implementations§

Source§

impl Debug for NativeRealtimeParser

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for NativeRealtimeParser

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.