Skip to content

Commit

Permalink
Finish interactive parser development example.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gohla committed Nov 22, 2023
1 parent 41e6949 commit bd7ff8e
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 411 deletions.
5 changes: 5 additions & 0 deletions src/3_min_sound/7_cycle/c_2_writing_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ impl<W: Write> WritingTracker<W> {
indentation: 0,
}
}

/// Gets the writer of this writing tracker.
pub fn writer(&self) -> &W { &self.writer }
/// Gets the mutable writer of this writing tracker.
pub fn writer_mut(&mut self) -> &mut W { &mut self.writer }
}

#[allow(dead_code)]
Expand Down
3 changes: 1 addition & 2 deletions src/3_min_sound/7_cycle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,4 @@ We will not do that in this tutorial, but feel free to solve those issues (and w
You can [download the source files up to this point](../../gen/3_min_sound/7_cycle/source.zip).
```

This is currently the end of the guided programming tutorial.
In the appendix chapters, we discuss PIE implementations and publications, related work, and future work.
In the next chapter, we will implement a "parser development" application using PIE, which can do batch builds but also provides an interactive parser development environment, using a single set of tasks.
23 changes: 11 additions & 12 deletions src/4_example/e_3_editor_buffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,20 @@ impl Editor {
// Draw grammar buffer on the left (`buffer_areas[0]`).
self.buffers[0].draw(frame, buffer_areas[0], self.active_buffer == 0);

{ // Draw example program buffers on the right (`buffer_areas[1]`).
let num_program_buffers = self.buffers.len() - 1;
// Split vertical space between example program buffers.
let program_buffer_areas = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Ratio(1, num_program_buffers as u32); num_program_buffers])
.split(buffer_areas[1]);
for ((buffer, area), i) in self.buffers[1..].iter_mut().zip(program_buffer_areas.iter()).zip(1..) {
buffer.draw(frame, *area, self.active_buffer == i);
}
// Draw example program buffers on the right (`buffer_areas[1]`).
let num_program_buffers = self.buffers.len() - 1;
// Split vertical space between example program buffers.
let program_buffer_areas = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Ratio(1, num_program_buffers as u32); num_program_buffers])
.split(buffer_areas[1]);
for ((buffer, area), i) in self.buffers[1..].iter_mut().zip(program_buffer_areas.iter()).zip(1..) {
buffer.draw(frame, *area, self.active_buffer == i);
}

// Draw help line on the last line (`root_areas[1]`).
let help = Paragraph::new("Interactive Parser Development. Press Esc to quit, ^T to switch active \
buffer.");
let help = Paragraph::new("Interactive Parser Development. Press Esc to quit, ^T to switch the active \
buffer.");
frame.render_widget(help, root_areas[1]);
})?;

Expand Down
155 changes: 155 additions & 0 deletions src/4_example/f_editor_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::fmt::Write;
use std::io;

use crossterm::event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::backend::{Backend, CrosstermBackend};
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Terminal;
use ratatui::widgets::Paragraph;

use pie::Pie;

use crate::Args;
use crate::editor::buffer::Buffer;
use crate::task::{Outputs, Tasks};

mod buffer;

/// Live parser development editor.
pub struct Editor {
buffers: Vec<Buffer>,
active_buffer: usize,
rule_name: String,
pie: Pie<Tasks, Result<Outputs, String>>,
}

impl Editor {
/// Create a new editor from `args`.
///
/// # Errors
///
/// Returns an error when creating a buffer fails.
pub fn new(args: Args) -> Result<Self, io::Error> {
let mut buffers = Vec::with_capacity(1 + args.program_file_paths.len());
buffers.push(Buffer::new(args.grammar_file_path)?); // First buffer is always the grammar buffer.
for path in args.program_file_paths {
buffers.push(Buffer::new(path)?); // Subsequent buffers are always example program buffers.
}

let pie = Pie::default();
let mut editor = Self { buffers, active_buffer: 0, rule_name: args.rule_name, pie };
editor.save_and_update_buffers(false);
Ok(editor)
}

/// Run the editor, drawing it into an alternate screen of the terminal.
pub fn run(&mut self) -> Result<(), io::Error> {
// Setup terminal for GUI rendering.
enable_raw_mode()?;
let mut backend = CrosstermBackend::new(io::stdout());
crossterm::execute!(backend, EnterAlternateScreen, EnableMouseCapture)?;
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;

// Draw and process events in a loop until a quit is requested or an error occurs.
let result = loop {
match self.draw_and_process_event(&mut terminal) {
Ok(false) => break Ok(()), // Quit requested
Err(e) => break Err(e), // Error
_ => {},
}
};

// First undo our changes to the terminal.
disable_raw_mode()?;
crossterm::execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?;
terminal.show_cursor()?;
// Then present the result to the user.
result
}

fn draw_and_process_event<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<bool, io::Error> {
terminal.draw(|frame| {
let root_areas = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Percentage(100), Constraint::Min(1)])
.split(frame.size());
let buffer_areas = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
.split(root_areas[0]);

// Draw grammar buffer on the left (`buffer_areas[0]`).
self.buffers[0].draw(frame, buffer_areas[0], self.active_buffer == 0);

// Draw example program buffers on the right (`buffer_areas[1]`).
let num_program_buffers = self.buffers.len() - 1;
// Split vertical space between example program buffers.
let program_buffer_areas = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Ratio(1, num_program_buffers as u32); num_program_buffers])
.split(buffer_areas[1]);
for ((buffer, area), i) in self.buffers[1..].iter_mut().zip(program_buffer_areas.iter()).zip(1..) {
buffer.draw(frame, *area, self.active_buffer == i);
}

// Draw help line on the last line (`root_areas[1]`).
let help = Paragraph::new("Interactive Parser Development. Press Esc to quit, ^T to switch the active \
buffer, ^S to save all buffers and provide feedback.");
frame.render_widget(help, root_areas[1]);
})?;

match crossterm::event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Release => return Ok(true), // Skip releases.
Event::Key(key) if key.code == KeyCode::Esc => return Ok(false),
Event::Key(key) if key.code == KeyCode::Char('t') && key.modifiers.contains(KeyModifiers::CONTROL) => {
self.active_buffer = (self.active_buffer + 1) % self.buffers.len();
}
Event::Key(key) if key.code == KeyCode::Char('s') && key.modifiers.contains(KeyModifiers::CONTROL) => {
self.save_and_update_buffers(true);
},
event => self.buffers[self.active_buffer].process_event(event), // Otherwise: forward to current buffer.
};

Ok(true)
}

fn save_and_update_buffers(&mut self, save: bool) {
for buffer in &mut self.buffers {
buffer.feedback_mut().clear();
}

if save {
for buffer in &mut self.buffers {
if let Err(error) = buffer.save_if_modified() {
// Ignore error: writing to String cannot fail.
let _ = writeln!(buffer.feedback_mut(), "Saving file failed: {}", error);
}
}
}

let mut session = self.pie.new_session();

let grammar_buffer = &mut self.buffers[0];
let compile_grammar_task = Tasks::compile_grammar(grammar_buffer.path());
match session.require(&compile_grammar_task) {
Err(error) => {
let _ = writeln!(grammar_buffer.feedback_mut(), "{}", error);
return; // Skip parsing if compiling grammar failed.
}
_ => {}
}

let compile_grammar_task = Box::new(compile_grammar_task);
for buffer in &mut self.buffers[1..] {
let task = Tasks::parse(&compile_grammar_task, buffer.path(), &self.rule_name);
let feedback = buffer.feedback_mut();
match session.require(&task) {
Err(error) => { let _ = writeln!(feedback, "{}", error); },
Ok(Outputs::Parsed(Some(output))) => { let _ = writeln!(feedback, "Parsing succeeded: {}", output); },
_ => {}
}
}
}
}
Loading

0 comments on commit bd7ff8e

Please sign in to comment.