Skip to content

Commit

Permalink
parser: allow empty identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed Jun 10, 2024
1 parent 6982cfc commit a096929
Show file tree
Hide file tree
Showing 10 changed files with 24 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Unreleased

**Improvements**

* Allow empty commands, keys, and prefixes.

# 0.5.0 (2024-05-31)

**Bug Fixes**
Expand Down
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
//!
//! ## Commands
//!
//! A [`Command`] must have a command name, which can be any arbitrary non-empty
//! A [`Command`] must have a command name, which can be any arbitrary
//! [string](#strings), e.g.:
//!
//! ```text
Expand All @@ -198,8 +198,9 @@
//!
//! * [**Arguments:**](Argument) any number of space-separated arguments.
//! These have a string [value](Argument::value), and optionally also a string
//! [key](Argument::key) as `key=value`. Values can be empty, and duplicate
//! keys are allowed by the parser (the runner can handle this as desired).
//! [key](Argument::key) as `key=value`. Keys and values can be empty, and
//! duplicate keys are allowed by the parser (the runner can handle this as
//! desired).
//!
//! ```text
//! command argument key=value
Expand Down
12 changes: 3 additions & 9 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ fn command(input: Span) -> IResult<Command> {
let silent = maybe_silent.is_some();

// The prefix and fail marker.
let (input, prefix) = opt(terminated(identifier, pair(tag(":"), space0)))(input)?;
let (input, prefix) = opt(terminated(string, pair(tag(":"), space0)))(input)?;
let (input, maybe_fail) = opt(terminated(char('!'), space0))(input)?;
let fail = maybe_fail.is_some();

// The command itself.
let line_number = input.location_line();
let (input, name) = identifier(input)?;
let (input, name) = string(input)?;
let (mut input, args) = many0(preceded(space1, argument))(input)?;

// If silenced, look for the closing brace.
Expand All @@ -124,7 +124,7 @@ fn command(input: Span) -> IResult<Command> {
/// Parses a single command argument, consisting of an argument value and
/// optionally a key separated by =.
fn argument(input: Span) -> IResult<Argument> {
if let Ok((input, (key, value))) = separated_pair(identifier, tag("="), opt(string))(input) {
if let Ok((input, (key, value))) = separated_pair(string, tag("="), opt(string))(input) {
return Ok((input, Argument { key: Some(key), value: value.unwrap_or_default() }));
}
let (input, value) = string(input)?;
Expand All @@ -148,12 +148,6 @@ fn output(input: Span) -> IResult<Span> {
recognize(many_till(anychar, pair(alt((line_ending, eof)), alt((line_ending, eof)))))(input)
}

/// Parses an identifier. This is any unquoted or quoted string that
/// isn't entirely empty.
fn identifier(input: Span) -> IResult<String> {
verify(string, |s: &str| !s.is_empty())(input)
}

/// Parses a string, both quoted (' or ") and unquoted.
fn string(input: Span) -> IResult<String> {
alt((unquoted_string, quoted_string('\''), quoted_string('"')))(input)
Expand Down
3 changes: 0 additions & 3 deletions tests/errors/empty_arg_key.error

This file was deleted.

2 changes: 0 additions & 2 deletions tests/errors/empty_arg_key.in

This file was deleted.

3 changes: 0 additions & 3 deletions tests/errors/empty_command.error

This file was deleted.

2 changes: 0 additions & 2 deletions tests/errors/empty_command.in

This file was deleted.

3 changes: 0 additions & 3 deletions tests/errors/empty_prefix.error

This file was deleted.

2 changes: 0 additions & 2 deletions tests/errors/empty_prefix.in

This file was deleted.

18 changes: 11 additions & 7 deletions tests/scripts/commands
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }],
---
Error: Command { name: "foo", args: [Argument { key: None, value: "bar" }], prefix: None, silent: false, fail: true, line_number: 23 }

# Both prefixes, commands, and argument identifiers can be whitespace, but not
# empty (tested under errors/).
# Prefixes, commands, and keys can be empty.
"": "" ""=""
---
: Command { name: "", args: [Argument { key: Some(""), value: "" }], prefix: Some(""), silent: false, fail: false, line_number: 28 }

# Prefixes, commands, and keys can be whitespace.
" ": " " " "=" "
---
: Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), silent: false, fail: false, line_number: 29 }
: Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), silent: false, fail: false, line_number: 33 }

# Empty argument values are fine.
# Empty argument keys and values are fine.
command ""
command arg=""
command arg=
---
Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, silent: false, fail: false, line_number: 34 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 35 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 36 }
Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, silent: false, fail: false, line_number: 38 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 39 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 40 }

0 comments on commit a096929

Please sign in to comment.