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

pub fn source(&self) -> &str

Returns the complete current source buffer.

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

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

Returns every retained complete, incomplete, or invalid cell.

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

pub fn state(&self) -> NativeCellState

Returns the aggregate state of the current source snapshot.

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

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.