Skip to content

Commit

Permalink
feat: expression examples
Browse files Browse the repository at this point in the history
  • Loading branch information
mightyiam committed Feb 17, 2024
1 parent 0f8d9fa commit 23a36fd
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 43 deletions.
44 changes: 34 additions & 10 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ pub(super) mod state;

use futures::{FutureExt, SinkExt, StreamExt};

use crate::repl::{
driver::{ReplCommand, ReplEvent},
example::ReplExample,
use crate::{
examples::Example,
expression::driver::{EvaluateExpression, ExpressionEvent},
repl::driver::{ReplCommand, ReplEvent},
};

use self::state::State;

pub(crate) struct Inputs {
pub(crate) repl_examples: Vec<ReplExample>,
pub(crate) examples: Vec<Example>,
pub(crate) repl_events: futures::stream::LocalBoxStream<'static, ReplEvent>,
pub(crate) expression_events: futures::stream::LocalBoxStream<'static, ExpressionEvent>,
}

pub(crate) struct Outputs {
pub(crate) execution_handle: futures::future::LocalBoxFuture<'static, ()>,
pub(crate) repl_commands: futures::stream::LocalBoxStream<'static, ReplCommand>,
pub(crate) expression_commands: futures::stream::LocalBoxStream<'static, EvaluateExpression>,
pub(crate) done: futures::future::LocalBoxFuture<'static, anyhow::Result<()>>,
pub(crate) eprintln_strings: futures::stream::LocalBoxStream<'static, String>,
}
Expand All @@ -25,32 +28,43 @@ pub(crate) struct Outputs {
enum OutputEvent {
Done(anyhow::Result<()>),
ReplCommand(ReplCommand),
ExpressionCommand(EvaluateExpression),
Eprintln(String),
}

#[derive(Debug)]
enum InputEvent {
ReplExample(ReplExample),
Example(Example),
ReplEvent(ReplEvent),
ExpressionEvent(ExpressionEvent),
}

pub(crate) fn app(inputs: Inputs) -> Outputs {
let Inputs {
repl_examples,
examples,
repl_events,
expression_events,
} = inputs;

let repl_examples = futures::stream::iter(repl_examples).map(InputEvent::ReplExample);
let examples = futures::stream::iter(examples).map(InputEvent::Example);

let repl_events = repl_events.map(InputEvent::ReplEvent);
let expression_events = expression_events.map(InputEvent::ExpressionEvent);

let input_events =
futures::stream::select_all([repl_examples.boxed_local(), repl_events.boxed_local()]);
let input_events = futures::stream::select_all([
examples.boxed_local(),
repl_events.boxed_local(),
expression_events.boxed_local(),
]);

let output_events = input_events
.scan(State::default(), |state, event| {
let output = match event {
InputEvent::ReplExample(repl_example) => state.repl_example(repl_example),
InputEvent::Example(example) => state.example(example),
InputEvent::ReplEvent(repl_event) => state.repl_event(repl_event),
InputEvent::ExpressionEvent(expression_event) => {
state.expression_event(expression_event)
}
};

let output = match output {
Expand All @@ -64,6 +78,8 @@ pub(crate) fn app(inputs: Inputs) -> Outputs {

let (eprintln_sender, eprintln_strings) = futures::channel::mpsc::unbounded::<String>();
let (repl_commands_sender, repl_commands) = futures::channel::mpsc::unbounded::<ReplCommand>();
let (expression_commands_sender, expression_commands) =
futures::channel::mpsc::unbounded::<EvaluateExpression>();
let (done_sender, done) = futures::channel::mpsc::unbounded::<anyhow::Result<()>>();

let execution_handle = output_events.for_each(move |output_event| match output_event {
Expand All @@ -81,6 +97,13 @@ pub(crate) fn app(inputs: Inputs) -> Outputs {
}
.boxed_local()
}
OutputEvent::ExpressionCommand(evaluate_expression) => {
let mut sender = expression_commands_sender.clone();
async move {
sender.send(evaluate_expression).await.unwrap();
}
.boxed_local()
}
OutputEvent::Eprintln(string) => {
let mut sender = eprintln_sender.clone();
async move {
Expand All @@ -93,6 +116,7 @@ pub(crate) fn app(inputs: Inputs) -> Outputs {
Outputs {
eprintln_strings: eprintln_strings.boxed_local(),
repl_commands: repl_commands.boxed_local(),
expression_commands: expression_commands.boxed_local(),
done: done
.into_future()
.map(|(next_item, _tail)| next_item.unwrap())
Expand Down
137 changes: 116 additions & 21 deletions src/app/state.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
pub(crate) mod expression_state;
pub(crate) mod repl_state;

use anyhow::bail;
use indoc::formatdoc;

use crate::{
example_id::ExampleId,
repl::{
driver::{ReplCommand, ReplEvent, ReplQuery},
example::ReplExample,
},
examples::Example,
expression::driver::{EvaluateExpression, ExpressionEvent},
repl::driver::{ReplCommand, ReplEvent, ReplQuery},
};

use self::repl_state::{ReplExampleState, ReplSessionExpecting, ReplSessionLive, ReplSessionState};
use self::{
expression_state::ExpressionExampleState,
repl_state::{ReplExampleState, ReplSessionExpecting, ReplSessionLive, ReplSessionState},
};

use super::OutputEvent;

Expand All @@ -18,16 +24,25 @@ pub(super) struct State {
}

impl State {
pub(super) fn repl_example(
&mut self,
repl_example: ReplExample,
) -> anyhow::Result<Vec<OutputEvent>> {
let id = repl_example.id.clone();
pub(super) fn example(&mut self, example: Example) -> anyhow::Result<Vec<OutputEvent>> {
let (id, example_state, event) = match example {
Example::Repl(example) => {
let example_id = example.id.clone();
let example_state = ExampleState::Repl(ReplExampleState::new(example));
let event = OutputEvent::ReplCommand(ReplCommand::Spawn(example_id.clone()));
(example_id, example_state, event)
}
Example::Expression(example) => {
let example_id = example.id.clone();
let example_state = ExampleState::Expression(ExpressionExampleState::Pending);
let event = OutputEvent::ExpressionCommand(EvaluateExpression(example));
(example_id, example_state, event)
}
};

self.examples
.insert(id.clone(), ReplExampleState::new(repl_example))?;
self.examples.insert(id.clone(), example_state)?;

Ok(vec![OutputEvent::ReplCommand(ReplCommand::Spawn(id))])
Ok(vec![event])
}

pub(super) fn repl_event(&mut self, repl_event: ReplEvent) -> anyhow::Result<Vec<OutputEvent>> {
Expand All @@ -45,7 +60,7 @@ impl State {
) -> anyhow::Result<Vec<OutputEvent>> {
let id = spawn?;

let session = self.examples.get_mut(&id)?;
let session = self.examples.get_mut_repl(&id)?;

if let ReplSessionState::Live(_) = &session.state {
return Err(anyhow::anyhow!("spawned session {session:?} already live"));
Expand Down Expand Up @@ -88,7 +103,7 @@ impl State {
id: ExampleId,
result: std::io::Result<u8>,
) -> anyhow::Result<Vec<OutputEvent>> {
let session_live = self.examples.get_mut(&id)?;
let session_live = self.examples.get_mut_repl(&id)?;
let session_live = session_live.state.live_mut()?;
let ch = result?;

Expand Down Expand Up @@ -152,7 +167,7 @@ impl State {
}

fn next_query(&mut self, id: &ExampleId) -> anyhow::Result<Vec<OutputEvent>> {
let session = self.examples.get_mut(id)?;
let session = self.examples.get_mut_repl(id)?;

let ReplSessionState::Live(session_live) = &mut session.state else {
anyhow::bail!("expected session {id} to be live");
Expand All @@ -175,7 +190,7 @@ impl State {
}

fn session_end(&mut self, id: &ExampleId) -> anyhow::Result<Vec<OutputEvent>> {
let session = self.examples.get_mut(id)?;
let session = self.examples.get_mut_repl(id)?;
session.state = ReplSessionState::Killing;
Ok(vec![
OutputEvent::ReplCommand(ReplCommand::Kill(id.clone())),
Expand All @@ -195,26 +210,81 @@ impl State {
.collect();
Ok(string)
}

pub(crate) fn expression_event_output(
&mut self,
expression_output: std::io::Result<(ExampleId, std::process::Output)>,
) -> anyhow::Result<Vec<OutputEvent>> {
let (example_id, expression_output) = expression_output?;

if !expression_output.status.success() {
let stderr = String::from_utf8_lossy(&expression_output.stderr);
bail!("{example_id}\n{stderr}")
}

if expression_output.stdout != b"null\n" {
let stdout = String::from_utf8_lossy(&expression_output.stdout);
let stdout = stdout.trim_end();

let message = formatdoc! {"
{example_id}
evaluated into non-null
note: examples must evaluate into null
value: {stdout}"};

bail!("{message}");
}

self.examples.remove(&example_id)?;

let mut events = vec![OutputEvent::Eprintln(Self::fmt_pass(&example_id))];

if self.examples.is_empty() {
events.push(OutputEvent::Done(Ok(())))
}

Ok(events)
}

pub(crate) fn expression_event(
&mut self,
expression_event: ExpressionEvent,
) -> Result<Vec<OutputEvent>, anyhow::Error> {
match expression_event {
ExpressionEvent::Spawn(result) => self.expression_event_spawn(result),
ExpressionEvent::Output(result) => self.expression_event_output(result),
}
}

fn expression_event_spawn(
&mut self,
result: Result<ExampleId, std::io::Error>,
) -> anyhow::Result<Vec<OutputEvent>> {
let example_id = result?;
let example_state = self.examples.get_mut_expression(&example_id)?;
*example_state = ExpressionExampleState::Spawned;
Ok(vec![])
}
}

#[derive(Debug, Default)]
pub(crate) struct ExamplesState(std::collections::BTreeMap<ExampleId, ReplExampleState>);
pub(crate) struct ExamplesState(std::collections::BTreeMap<ExampleId, ExampleState>);

impl ExamplesState {
pub(crate) fn insert(&mut self, id: ExampleId, state: ReplExampleState) -> anyhow::Result<()> {
pub(crate) fn insert(&mut self, id: ExampleId, state: ExampleState) -> anyhow::Result<()> {
if self.0.insert(id.clone(), state).is_some() {
anyhow::bail!("duplicate session id {id:?}");
};
Ok(())
}

pub(crate) fn get_mut(&mut self, id: &ExampleId) -> anyhow::Result<&mut ReplExampleState> {
pub(crate) fn get_mut(&mut self, id: &ExampleId) -> anyhow::Result<&mut ExampleState> {
self.0
.get_mut(id)
.ok_or_else(|| anyhow::anyhow!("repl session not found {id:?}"))
}

pub(crate) fn remove(&mut self, id: &ExampleId) -> anyhow::Result<ReplExampleState> {
pub(crate) fn remove(&mut self, id: &ExampleId) -> anyhow::Result<ExampleState> {
self.0
.remove(id)
.ok_or_else(|| anyhow::anyhow!("repl session not found {id:?}"))
Expand All @@ -223,4 +293,29 @@ impl ExamplesState {
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}

fn get_mut_repl(&mut self, id: &ExampleId) -> anyhow::Result<&mut ReplExampleState> {
let example_state = self.get_mut(id)?;
let ExampleState::Repl(repl_example_state) = example_state else {
anyhow::bail!("expected repl example state");
};
Ok(repl_example_state)
}

fn get_mut_expression(
&mut self,
id: &ExampleId,
) -> anyhow::Result<&mut ExpressionExampleState> {
let example_state = self.get_mut(id)?;
let ExampleState::Expression(expression_example_state) = example_state else {
anyhow::bail!("expected expression example state");
};
Ok(expression_example_state)
}
}

#[derive(Debug)]
pub(crate) enum ExampleState {
Repl(ReplExampleState),
Expression(ExpressionExampleState),
}
6 changes: 6 additions & 0 deletions src/app/state/expression_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[derive(Debug, Default)]
pub(crate) enum ExpressionExampleState {
#[default]
Pending,
Spawned,
}
28 changes: 22 additions & 6 deletions src/examples.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use crate::expression::ExpressionExample;
use crate::repl::example::ReplExample;
use crate::repl::example::NIX_REPL_LANG_TAG;
use itertools::Itertools;

pub(crate) fn obtain(glob: &str) -> anyhow::Result<Vec<ReplExample>> {
#[derive(Debug, Clone)]
pub(crate) enum Example {
Repl(ReplExample),
Expression(ExpressionExample),
}

pub(crate) fn obtain(glob: &str) -> anyhow::Result<Vec<Example>> {
glob::glob(glob)?
.map(|path| {
let path = camino::Utf8PathBuf::try_from(path?)?;
Expand All @@ -27,15 +34,24 @@ pub(crate) fn obtain(glob: &str) -> anyhow::Result<Vec<ReplExample>> {
.filter_map(|(path, ast)| {
if let comrak::nodes::NodeValue::CodeBlock(code_block) = ast.value {
let comrak::nodes::NodeCodeBlock { info, literal, .. } = code_block;
if let Some(NIX_REPL_LANG_TAG) = info.split_ascii_whitespace().next() {
Some((path, ast.sourcepos.start.line, literal.clone()))
} else {
None
match info.split_ascii_whitespace().next() {
Some(NIX_REPL_LANG_TAG) => {
let line = ast.sourcepos.start.line;
let repl_example =
ReplExample::try_new(path, line, literal.clone()).map(Example::Repl);
Some(repl_example)
}
Some("nix") => {
let line = ast.sourcepos.start.line;
let expression_example =
ExpressionExample::new(path, line, literal.clone());
Some(Ok(Example::Expression(expression_example)))
}
_ => None,
}
} else {
None
}
})
.map(|(path, line, contents)| ReplExample::try_new(path, line, contents))
.try_collect()
}
16 changes: 16 additions & 0 deletions src/expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pub(crate) mod driver;

use crate::example_id::ExampleId;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExpressionExample {
pub(crate) id: ExampleId,
pub(crate) expression: String,
}

impl ExpressionExample {
pub(crate) fn new(path: camino::Utf8PathBuf, line: usize, expression: String) -> Self {
let id = ExampleId::new(path, line);
Self { id, expression }
}
}
Loading

0 comments on commit 23a36fd

Please sign in to comment.