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?
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}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?
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}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?
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}Sourcepub fn source(&self) -> &str
pub fn source(&self) -> &str
Returns the complete current source buffer.
Examples found in repository?
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}Sourcepub fn cells(&self) -> &[NativeRealtimeCell]
pub fn cells(&self) -> &[NativeRealtimeCell]
Returns every retained complete, incomplete, or invalid cell.
Examples found in repository?
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}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?
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}Sourcepub fn state(&self) -> NativeCellState
pub fn state(&self) -> NativeCellState
Returns the aggregate state of the current source snapshot.
Examples found in repository?
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}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.