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
impl NativeRealtimeParser
Sourcepub fn new(source: impl Into<String>) -> Self
pub fn new(source: impl Into<String>) -> Self
Parses and retains an initial source snapshot.
Examples found in repository?
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}Sourcepub fn apply_edit(
&mut self,
edit: &TextEdit,
) -> Result<NativeCellState, NativeRealtimeEditError>
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?
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}Sourcepub fn append(&mut self, source: impl Into<String>) -> NativeCellState
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?
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}Sourcepub fn source(&self) -> &str
pub fn source(&self) -> &str
Returns the complete current source buffer.
Examples found in repository?
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}Sourcepub fn cells(&self) -> &[NativeRealtimeCell]
pub fn cells(&self) -> &[NativeRealtimeCell]
Returns every retained complete, incomplete, or invalid cell.
Examples found in repository?
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}Sourcepub fn reparsed_cells(&self) -> Range<usize> ⓘ
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?
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}Sourcepub fn state(&self) -> NativeCellState
pub fn state(&self) -> NativeCellState
Returns the aggregate state of the current source snapshot.
Examples found in repository?
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}Sourcepub fn cell_stream(&self) -> CellStream
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.