Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/special symbols #14

Merged
merged 3 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 113 additions & 3 deletions crates/recipe-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ fn parse_recipe_ref<'a>(

/// Tokens are separated into words
fn parse_word<'a>(input: &mut Input<'a>) -> PResult<&'a str> {
take_till(1.., (' ', '\t', '\r', '\n')).parse_next(input)
take_till(1.., (' ', '\t', '\r', '\n', '\'', '`')).parse_next(input)
}

fn parse_metadata<'a>(input: &mut Input<'a>) -> PResult<(&'a str, &'a str)> {
Expand Down Expand Up @@ -187,10 +187,25 @@ fn parse_backstory<'a>(input: &mut Input<'a>) -> PResult<&'a str> {
.parse_next(input)
}

/// Symbols that create conflict when parsing
///
/// If you have a recipe like:
///
/// ```recp
/// Take the l'{ingredient}
/// ```
///
/// There's a word connected to the ingredient by a symbol, in order to be prevent
/// the `parse_word` from ingesting the chunk `l'{ingredient}` as a word, we need
/// to tell `parse_word` to stop at this character and also we need to catch it here.
fn parse_special_symbols<'a>(input: &mut Input<'a>) -> PResult<&'a str> {
alt(("(", "'", "`")).parse_next(input)
}
/* ****************
* The main parser
**************** */

/// A recipe string is parsed into many of these tokens
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
Expand All @@ -199,26 +214,74 @@ fn parse_backstory<'a>(input: &mut Input<'a>) -> PResult<&'a str> {
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
pub enum Token<'a> {
/// Relevant information of a recipe that doesn't make the recipe itself
///
/// Example:
/// ```recp
/// >> name: Salad
/// ```
Metadata {
key: &'a str,
value: &'a str,
},

/// Represents a recipe ingredient
///
/// Example
///
/// ```recp
/// {tomato}(1 kg)
/// ```
Ingredient {
name: &'a str,
quantity: Option<&'a str>,
unit: Option<&'a str>,
},
// Reference to another recipe

/// Link to another recipe
///
/// Example
///
/// ```recp
/// @{path/recipe}(30 ml)
/// ```
RecipeRef {
name: &'a str,
quantity: Option<&'a str>,
unit: Option<&'a str>,
},

/// Mark for a timer
///
/// Example
///
/// ```recp
/// t{25 minutes}
/// ```
Timer(&'a str),

/// Mark for a material required
///
/// Example
///
/// ```recp
/// &{blender}
/// ```
Material(&'a str),

Word(&'a str),
Space(&'a str),
Comment(&'a str),

/// Information, story or notes about a recipe
///
/// Example
///
/// ```recp
/// my recipe
/// ---
/// shared by my best friend
/// ```
Backstory(&'a str),
}

Expand Down Expand Up @@ -285,7 +348,7 @@ pub fn recipe_value<'a>(input: &mut Input<'a>) -> PResult<Token<'a>> {
}),
parse_backstory.map(|v| Token::Backstory(v)),
parse_comment.map(|v| Token::Comment(v)),
"(".map(|v| Token::Word(v)),
parse_special_symbols.map(|v| Token::Word(v)),
parse_word.map(|v| Token::Word(v)),
space1.map(|v| Token::Space(v)),
multispace1.map(|v| Token::Space(v)),
Expand Down Expand Up @@ -627,6 +690,53 @@ mod test {
println!("{:?}", recipe);
}

#[rstest]
#[case("l'{ingredient}", vec![
Token::Word("l"),
Token::Word("'"),
Token::Ingredient {
name: "ingredient",
quantity: None,
unit: None,
},
])]
#[case("l'&{material}", vec![
Token::Word("l"),
Token::Word("'"),
Token::Material("material"),
])]
#[case("l't{mytimer}", vec![
Token::Word("l"),
Token::Word("'"),
Token::Timer("mytimer"),
])]
#[case("l`{ingredient}", vec![
Token::Word("l"),
Token::Word("`"),
Token::Ingredient {
name: "ingredient",
quantity: None,
unit: None,
},
])]
#[case("l`&{material}", vec![
Token::Word("l"),
Token::Word("`"),
Token::Material("material"),
])]
#[case("l`t{mytimer}", vec![
Token::Word("l"),
Token::Word("`"),
Token::Timer("mytimer"),
])]
fn test_parse_ingredients_without_spaces(#[case] input: &str, #[case] expected: Vec<Token>) {
let recipe = parse(input).expect("parse failed");

println!("{:?}", recipe);

assert_eq!(expected, recipe);
}

#[test]
fn test_parse_with_backstory_ok() {
let input = "Foo. \n---\nA backstory";
Expand Down
39 changes: 39 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 48 additions & 26 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
nixpkgs-lib.follows = "nixpkgs";
};
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = inputs@{ flake-parts, nci, ... }:
outputs =
inputs@{ flake-parts, nci, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
# To import a flake module
Expand All @@ -24,34 +29,51 @@
# 3. Add here: foo.flakeModule
nci.flakeModule
];
systems = [ "x86_64-linux" "aarch64-darwin" "x86_64-darwin" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
# Per-system attributes can be defined here. The self' and inputs'
# module parameters provide easy access to attributes of the same
# system.
systems = [
"x86_64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
perSystem =
{
config,
self',
inputs',
pkgs,
system,
...
}:
{
# Per-system attributes can be defined here. The self' and inputs'
# module parameters provide easy access to attributes of the same
# system.

nci.projects."recipe-lang" = {
path = ./.;
# export all crates (packages and devshell) in flake outputs
# alternatively you can access the outputs and export them yourself
export = true;
};
nci.projects."recipe-lang" = {
path = ./.;
# export all crates (packages and devshell) in flake outputs
# alternatively you can access the outputs and export them yourself
export = true;
};

# configure crates
nci.crates = {
"recp" = { };
"recipe-parser" = { };
};
# configure crates
nci.crates = {
"recp" = { };
"recipe-parser" = { };
};

# export the project devshell as the default devshell
# devShells.default = config.nci.outputs."recipe-lang".devShell;
devShells.default = pkgs.mkShell {
inputsFrom = [ config.nci.outputs."recipe-lang".devShell ];
packages = [ pkgs.cargo-dist pkgs.rustup ];
# export the project devshell as the default devshell
# devShells.default = config.nci.outputs."recipe-lang".devShell;
devShells.default = pkgs.mkShell {
inputsFrom = [ config.nci.outputs."recipe-lang".devShell ];
packages = [
pkgs.cargo-dist
pkgs.rustup
inputs'.fenix.packages.default.toolchain
];
};
# export the release package of the crate as default package
packages.default = config.nci.outputs."recp".packages.release;
packages.recp = config.nci.outputs."recp".packages.release;
};
# export the release package of the crate as default package
packages.default = config.nci.outputs."recp".packages.release;
packages.recp = config.nci.outputs."recp".packages.release;
};
};
}