Skip to content

Commit

Permalink
Add handling of pub templates
Browse files Browse the repository at this point in the history
- Now templates with a pub prefix are collected in the pub_templates Ast
  struct field
- Now, when parsing, the result may either be a scene or a template
  library.
- Template libraries are mutually exclusive with scene files.
  • Loading branch information
nicopap committed Nov 15, 2023
1 parent 3f32f53 commit 2ba6451
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 365 deletions.
48 changes: 48 additions & 0 deletions chirp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,54 @@ their type.

See [`parse_dsl_impl::type_parsers`] for details.

## Formal specification

### Grammar

```ungrammar
TokenTree
= 'ident'
| '(' (TokenTree)* ')'
| '[' (TokenTree)* ']'
| '{' (TokenTree)* '}'
| StringLit
Method = 'ident' ('(' (TokenTree)* ')')?
Statement
= 'code' '(' 'ident' ')'
| 'Entity' StatementTail
| 'ident' '!' '(' (TokenTree (',' TokenTree)*)? ')' (StatementTail)?
| 'ident' StatementTail
| StringLit StatementTail
StatementTail
= '(' (Method)* ')' ('{' (Statement)* '}')?
| '{' (Statement)* '}'
Path = 'ident' | StringLit
ImportItem = '(' 'ident' as 'ident' ')' | 'ident'
Import = 'use' Path ((ImportItem)*)
Fn = ('pub')? 'fn' 'ident' '(' ('ident' (',' 'ident')*)? ')' '{' Statement '}'
ChirpFile = (Import)* (Fn)* Statement
```

* Notice how `StatementTail` is **never empty**. This ensures that syntax errors
such as `My Entity Name()` are detected and reported correctly.
* `'ident'` is any series of character that is not a whitespace or delimiter such
as `[]{}()"'!,`, so this includes surprising stuff such as `+-_` and `234`.
* `StringLit` works similarly to a rust string literal.
* `TokenTree` aims to work like a [rust `TokenTree`], may accept more than what
the rust grammar accepts (within acceptable limits) for performance reason.
The inside of the parenthesis is passed as-is to `ParseDsl::method`.

### Comments

Currently, only `//` line comments are supported.

[rust `TokenTree`]: https://doc.rust-lang.org/reference/macros.html#macro-invocation


## Tooling

Tooling for the `chirp` file format is available at <https://github.com/nicopap/cuicui_chirp_tooling>
Expand Down
60 changes: 32 additions & 28 deletions chirp/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use winnow::BStr;

use crate::parse_dsl::{self, MethodCtx, ParseDsl};
use crate::parser::{self, chirp_file, Arguments, ChirpFile, FnIndex, Input, Name};
use crate::InterpretResult;

type Span = (u32, u32);

Expand Down Expand Up @@ -76,6 +77,36 @@ impl InterpError {
err.downcast_ref().and_then(ReflectError::maybe_offset)
}
}

pub(crate) fn interpret<'a, D: ParseDsl>(
input_u8: &[u8],
builder: &'a mut EntityCommands<'_, '_, 'a>,
load_ctx: Option<&'a mut LoadContext>,
reg: &'a TypeRegistry,
handles: &'a Handles,
) -> InterpretResult<()> {
let input = Input::new(input_u8, ());
let ast = match chirp_file(input) {
parser::ParseResult::Ast(ast) => ast,
parser::ParseResult::TemplateLibrary(template_library) => {
return InterpretResult::TemplateLibrary(template_library);
}
parser::ParseResult::Err(err, span) => {
let error = SpannedError::new::<D>(err, span);
return InterpretResult::Err(Errors::new(vec![error], input_u8, load_ctx.as_deref()));
}
};
let chirp_file = ChirpFile::new(input, ast.as_ref());
let mut interpreter = Interpreter::<D>::new(builder, load_ctx, reg, handles);
chirp_file.interpret(&mut interpreter);
if interpreter.errors.is_empty() {
InterpretResult::Ok(())
} else {
let ctx = interpreter.load_ctx.as_deref();
InterpretResult::Err(Errors::new(interpreter.errors, input_u8, ctx))
}
}

// TODO(feat): print call stack.
#[derive(Debug, Error, Diagnostic)]
#[error("{error}")]
Expand Down Expand Up @@ -202,7 +233,7 @@ impl Debug for LoadCtx<'_, '_> {
.finish()
}
}
pub(crate) struct Interpreter<'w, 's, 'a, 'l, D> {
struct Interpreter<'w, 's, 'a, 'l, D> {
ctx: LoadCtx<'a, 'a>,
cmds: &'a mut Commands<'w, 's>,
parent_chain: SmallVec<[Entity; 2]>,
Expand All @@ -227,33 +258,6 @@ impl<'w, 's, 'a, 'l, D> fmt::Debug for Interpreter<'w, 's, 'a, 'l, D> {
}
}

impl<'w, 's, 'a, 'l> Interpreter<'w, 's, 'a, 'l, ()> {
pub(crate) fn interpret<D: ParseDsl>(
input_u8: &[u8],
builder: &'a mut EntityCommands<'w, 's, 'a>,
load_ctx: Option<&'a mut LoadContext<'l>>,
reg: &'a TypeRegistry,
handles: &'a Handles,
) -> Result<(), Errors> {
let input = Input::new(input_u8, ());
let ast = match chirp_file(input) {
Ok(v) => v,
Err((err, span)) => {
let error = SpannedError::new::<D>(err, span);
return Err(Errors::new(vec![error], input_u8, load_ctx.as_deref()));
}
};
let chirp_file = ChirpFile::new(input, ast.as_ref());
let mut interpreter = Interpreter::<D>::new(builder, load_ctx, reg, handles);
chirp_file.interpret(&mut interpreter);
if interpreter.errors.is_empty() {
Ok(())
} else {
let ctx = interpreter.load_ctx.as_deref();
Err(Errors::new(interpreter.errors, input_u8, ctx))
}
}
}
impl<'w, 's, 'a, 'l, D: ParseDsl> Interpreter<'w, 's, 'a, 'l, D> {
fn new(
builder: &'a mut EntityCommands<'w, 's, 'a>,
Expand Down
65 changes: 47 additions & 18 deletions chirp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ macro_rules! log_miette_error {
use bevy::asset::LoadContext;
use bevy::ecs::{prelude::*, system::SystemState};
use bevy::reflect::TypeRegistry;

use crate::interpret::Interpreter;
use bevy::scene::Scene;

pub use anyhow;
/// `impl` block macro to implement [`ParseDsl`].
Expand All @@ -50,6 +49,7 @@ pub use cuicui_chirp_macros::parse_dsl_impl;
pub use interpret::{Handles, InterpError};
pub use loader::{Chirp, ChirpBundle, ChirpState, WorldHandles};
pub use parse_dsl::{MethodCtx, ParseDsl};
pub use parser::TemplateLibrary;
pub use reflect::ReflectDsl;

mod parser;
Expand Down Expand Up @@ -85,6 +85,43 @@ pub mod bevy_types {
pub use bevy::prelude::Entity;
}

/// Result of the [`ChirpReader::interpret`] method.
pub enum InterpretResult<T> {
/// The provided chirp file is a valid scene file.
Ok(T),
/// The provided chirp file is a valid template library.
TemplateLibrary(TemplateLibrary),
/// The provided file is not a valid chirp file, and interpreting caused errors.
Err(interpret::Errors),
}
impl<T> InterpretResult<T> {
fn map<U>(self, f: impl FnOnce(T) -> U) -> InterpretResult<U> {
match self {
Self::Ok(ok) => InterpretResult::Ok(f(ok)),
Self::TemplateLibrary(v) => InterpretResult::TemplateLibrary(v),
Self::Err(e) => InterpretResult::Err(e),
}
}
fn is_ok(&self) -> bool {
matches!(self, Self::Ok(_))
}
}

/// Interpret `input` as a chirp file.
#[must_use]
pub fn interpret_chirp<D: ParseDsl>(
handles: &Handles,
load_context: Option<&mut LoadContext>,
registry: &TypeRegistry,
input: &[u8],
) -> InterpretResult<(Entity, Scene)> {
let mut scene = Scene::new(World::new());
let mut chirp_reader = ChirpReader::new(&mut scene.world);
chirp_reader
.interpret::<D>(handles, load_context, registry, input)
.map(|entity| (entity, scene))
}

/// Deserialized `dsl!` object.
///
/// Use [`ChirpReader::new`] to create a `ChirpReader` that will spawn stuff into
Expand All @@ -105,13 +142,12 @@ impl<'a> ChirpReader<'a> {
pub fn new(world: &'a mut World) -> Self {
Self { world }
}
/// Create a [`ChirpReader`] from arbitrary byte slices.
/// Interpret `input` as a chirp file, writting it into [`Self::world`].
///
/// This directly interprets the input as a chirp file and creates a bevy
/// scene.
/// The returned `Entity` is the root in `world` of the chirp file.
///
/// # Errors
/// If the input is an invalid `chirp` file. If this returns `Err`, then
/// If `input` is an invalid `chirp` file. If this returns `Err`, then
/// [`Self::world`] will be in an invalid partially-applied state.
///
/// Possible errors include:
Expand All @@ -129,12 +165,12 @@ impl<'a> ChirpReader<'a> {
load_context: Option<&mut LoadContext>,
registry: &TypeRegistry,
input: &[u8],
) -> Result<Entity, interpret::Errors> {
) -> InterpretResult<Entity> {
let mut state = SystemState::<Commands>::new(self.world);
let mut cmds = state.get_mut(self.world);
let mut cmds = cmds.spawn_empty();
let id = cmds.id();
let result = Interpreter::interpret::<D>(input, &mut cmds, load_context, registry, handles);
let result = interpret::interpret::<D>(input, &mut cmds, load_context, registry, handles);

if result.is_ok() {
state.apply(self.world);
Expand All @@ -156,17 +192,10 @@ impl<'a> ChirpReader<'a> {
registry: &TypeRegistry,
input: &[u8],
) -> bool {
let mut state = SystemState::<Commands>::new(self.world);
let mut cmds = state.get_mut(self.world);
let mut cmds = cmds.spawn_empty();
let result = Interpreter::interpret::<D>(input, &mut cmds, load_context, registry, handles);

if let Err(err) = &result {
let result = self.interpret::<D>(handles, load_context, registry, input);
if let InterpretResult::Err(err) = &result {
log_miette_error!(err);
false
} else {
state.apply(self.world);
true
}
result.is_ok()
}
}
40 changes: 0 additions & 40 deletions chirp/src/loader/internal.rs

This file was deleted.

22 changes: 17 additions & 5 deletions chirp/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ use bevy::transform::TransformSystem;
use bevy::utils::get_short_name;
use thiserror::Error;

use crate::{Handles, ParseDsl};
use crate::{interpret_chirp, Handles, InterpretResult, ParseDsl};

use spawn::Chirp_;
pub use spawn::{Chirp, ChirpState};

mod internal;
mod scene;
pub(super) mod spawn;

Expand Down Expand Up @@ -144,7 +144,7 @@ impl<D: ParseDsl + 'static> AssetLoader for ChirpLoader<D> {
&'a self,
reader: &'a mut bevy::asset::io::Reader,
_: &'a Self::Settings,
load_context: &'a mut LoadContext,
ctx: &'a mut LoadContext,
) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, std::io::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
Expand All @@ -155,9 +155,21 @@ impl<D: ParseDsl + 'static> AssetLoader for ChirpLoader<D> {
error!("Can't read handles in ChirpLoader<{name}>");
return Ok(Chirp(spawn::Chirp_::LoadError));
};
let chirp = internal::Loader::<D>::new(load_context, &registry, &handles).load(&bytes);
let (handles, reg) = (&*handles, &*registry);
let chirp = match interpret_chirp::<D>(handles, Some(ctx), reg, &bytes) {
InterpretResult::TemplateLibrary(template_library) => {
Chirp_::TemplateLibrary(template_library)
}
InterpretResult::Ok((root, scene)) => {
Chirp_::Loaded(root, ctx.add_labeled_asset("Scene".to_owned(), scene))
}
InterpretResult::Err(errors) => {
log_miette_error!(&errors);
Chirp_::Error(errors)
}
};
drop(registry);
let path = load_context.path().to_string_lossy();
let path = ctx.path().to_string_lossy();
info!("Complete loading of chirp: {path}");
Ok(Chirp(chirp))
})
Expand Down
7 changes: 5 additions & 2 deletions chirp/src/loader/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bevy::scene::Scene;
use thiserror::Error;

use super::scene::{self, ChirpInstance};
use crate::interpret;
use crate::{interpret, TemplateLibrary};

#[allow(missing_docs)] // allow: described by error message.
#[derive(Debug, Error)]
Expand Down Expand Up @@ -54,7 +54,10 @@ pub struct Chirp(pub(crate) Chirp_);
#[derive(Debug, TypePath)]
pub enum Chirp_ {
/// The chirp file loaded successfully and holds the given [`Scene`].
Loaded(Entity, Handle<Scene>),
Loaded(Entity, Handle<Scene>), // `Entity` is the root in the `Scene`.
/// The chirp file is a template library, it doesn't have a `Scene`, it
/// is used as a dependency for other [`Chirp`] files.
TemplateLibrary(TemplateLibrary),
/// The chirp file failed to load with the given [`anyhow::Error`].
///
/// Note: this exists because this enables us to use hot reloading even
Expand Down
Loading

0 comments on commit 2ba6451

Please sign in to comment.