Skip to content

Commit

Permalink
tweak: Error has basic parse mode without fields
Browse files Browse the repository at this point in the history
  • Loading branch information
dhedey committed Jan 21, 2025
1 parent 230ceb9 commit b874009
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 85 deletions.
17 changes: 12 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ I considered disallowing commands like `[!set! #x =]` and requiring `[!set! #x =

### To come

* Accept `[!error! <message>]` as well
* `[!split! { stream: X, separator: X, drop_empty?: false, drop_trailing_empty?: true, }]` and `[!comma_split! ...]`
* `[!zip! ([Hello Goodbye] [World Friend])]` => `[(Hello World), (Goodbye Friend)]` and/or `[!zip! { streams: (#countries #flags #capitals), trim_to_shortest?: false }]` with `InterpretValue<AnyGrouped<Repeated<CodeInput>>>`
* Add casts of other integers to char, via `char::from_u32(u32::try_from(x))`
* Add more tests
* e.g. for various expressions
* e.g. for long sums
* Rework `Error` as:
* `ParseResult` with `ParseError::LowLevel(syn::Error)` | `ParseError::Contextual(syn::Error)`
* `ExecutionInterrupt` with `ExecutionInterrupt::Err(syn::Error)` | `ExecutionInterrupt::ControlFlow(..)`
* Basic place parsing
* Introduce `[!let! #..x = Hello World]` does parsing and is equivalent to `[!set! #x = Hello World]`
* In parse land: #x matches a single token, #..x consumes the rest of a stream
Expand All @@ -47,19 +49,24 @@ I considered disallowing commands like `[!set! #x =]` and requiring `[!set! #x =
* `[!LITERAL! #x]` / `[!IDENT! #x]` bindings
* `[!OPTIONAL! ...]` and/or possibly `#(..)?` and `[!is_set! #x]`?
* Groups or `[!GROUP! ...]`
* Regarding `#(..)+` and `#(..)*`...
* Any binding could be set to an array, but it gets complicated fast with nested bindings.
* Instead for now, we could push people towards capturing the input and parsing it with for loops and matches.
* `#x` binding reads a token tree
* `#x` binding reads a token tree and appends its contents into `#x`
* `#..x` binding reads the rest of the stream... until the following raw token stream is detected (if at all). It must be followed by one or more raw tokens,
or the end of the stream.
* `#+x` binding reads a token tree and appends it to `#x` as the full token tree
* `#..+x` reads a stream and appends it to `#x`
* `[!REPEATED! ...]` or `[!PUNCTUATED! ...]` also forbid `#x` bindings inside of them unless a `[!settings!]` has been overriden
* Regarding `#(..)+` and `#(..)*`...
* Any binding could be set to an array, but it gets complicated fast with nested bindings.
* Instead for now, we could push people towards capturing the input and parsing it with for loops and matches.
* `[!RAW!]` for e.g. `[!while_parse! [!RAW! from] from #X]`
* `[!match!]` (with `#..x` as a catch-all)
* Check all `#[allow(unused)]` and remove any which aren't needed
* Rework expression parsing, in order to:
* Fix comments in the expression files
* Enable lazy && and ||
* Enable support for code blocks { .. } in expressions, and remove hacks where expression parsing stops at {} or .
* Make InterpretedStream an enum of either `Raw` or `Interpreted` including recursively (with `InterpretedTokenTree`) to fix rust-analyzer
* Push fork of syn::TokenBuffer to 0.4 to permit `[!parse_while! [!PARSE! ...] from #x { ... }]`
* Work on book
* Input paradigms:
* Streams
Expand Down
89 changes: 21 additions & 68 deletions src/commands/concat_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,17 @@ use crate::internal_prelude::*;
// Helpers
//========

fn string_literal(value: &str, span: Span) -> Literal {
let mut literal = Literal::string(value);
literal.set_span(span);
literal
}

fn parse_literal(value: &str, span: Span) -> Result<Literal> {
let mut literal = Literal::from_str(value)
.map_err(|err| span.error(format!("`{}` is not a valid literal: {:?}", value, err,)))?;
literal.set_span(span);
Ok(literal)
}

fn parse_ident(value: &str, span: Span) -> Result<Ident> {
let mut ident = parse_str::<Ident>(value)
.map_err(|err| span.error(format!("`{}` is not a valid ident: {:?}", value, err,)))?;
ident.set_span(span);
Ok(ident)
}

fn concat_into_string(
input: InterpretationStream,
interpreter: &mut Interpreter,
conversion_fn: impl Fn(&str) -> String,
) -> Result<Literal> {
let output_span = input.span();
let concatenated = concat_recursive(input.interpret_to_new_stream(interpreter)?);
let string_literal = string_literal(&conversion_fn(&concatenated), output_span);
Ok(string_literal)
let concatenated = input
.interpret_to_new_stream(interpreter)?
.concat_recursive();
let value = conversion_fn(&concatenated);
Ok(Literal::string(&value).with_span(output_span))
}

fn concat_into_ident(
Expand All @@ -41,8 +23,13 @@ fn concat_into_ident(
conversion_fn: impl Fn(&str) -> String,
) -> Result<Ident> {
let output_span = input.span();
let concatenated = concat_recursive(input.interpret_to_new_stream(interpreter)?);
let ident = parse_ident(&conversion_fn(&concatenated), output_span)?;
let concatenated = input
.interpret_to_new_stream(interpreter)?
.concat_recursive();
let value = conversion_fn(&concatenated);
let ident = parse_str::<Ident>(&value)
.map_err(|err| output_span.error(format!("`{}` is not a valid ident: {:?}", value, err,)))?
.with_span(output_span);
Ok(ident)
}

Expand All @@ -52,52 +39,18 @@ fn concat_into_literal(
conversion_fn: impl Fn(&str) -> String,
) -> Result<Literal> {
let output_span = input.span();
let concatenated = concat_recursive(input.interpret_to_new_stream(interpreter)?);
let literal = parse_literal(&conversion_fn(&concatenated), output_span)?;
let concatenated = input
.interpret_to_new_stream(interpreter)?
.concat_recursive();
let value = conversion_fn(&concatenated);
let literal = Literal::from_str(&value)
.map_err(|err| {
output_span.error(format!("`{}` is not a valid literal: {:?}", value, err,))
})?
.with_span(output_span);
Ok(literal)
}

fn concat_recursive(arguments: InterpretedStream) -> String {
fn concat_recursive_internal(output: &mut String, arguments: TokenStream) {
for token_tree in arguments {
match token_tree {
TokenTree::Literal(literal) => match literal.content_if_string_like() {
Some(content) => output.push_str(&content),
None => output.push_str(&literal.to_string()),
},
TokenTree::Group(group) => match group.delimiter() {
Delimiter::Parenthesis => {
output.push('(');
concat_recursive_internal(output, group.stream());
output.push(')');
}
Delimiter::Brace => {
output.push('{');
concat_recursive_internal(output, group.stream());
output.push('}');
}
Delimiter::Bracket => {
output.push('[');
concat_recursive_internal(output, group.stream());
output.push(']');
}
Delimiter::None => {
concat_recursive_internal(output, group.stream());
}
},
TokenTree::Punct(punct) => {
output.push(punct.as_char());
}
TokenTree::Ident(ident) => output.push_str(&ident.to_string()),
}
}
}

let mut output = String::new();
concat_recursive_internal(&mut output, arguments.into_token_stream());
output
}

macro_rules! define_literal_concat_command {
(
$command_name:literal => $command:ident: $output_fn:ident($conversion_fn:expr)
Expand Down
44 changes: 38 additions & 6 deletions src/commands/core_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,19 @@ impl NoOutputCommandDefinition for SettingsCommand {

#[derive(Clone)]
pub(crate) struct ErrorCommand {
inputs: ErrorInputs,
inputs: EitherErrorInput,
}

impl CommandType for ErrorCommand {
type OutputKind = OutputKindNone;
}

#[derive(Clone)]
enum EitherErrorInput {
Fields(ErrorInputs),
JustMessage(InterpretationStream),
}

define_field_inputs! {
ErrorInputs {
required: {
Expand All @@ -185,15 +191,41 @@ impl NoOutputCommandDefinition for ErrorCommand {
const COMMAND_NAME: &'static str = "error";

fn parse(arguments: CommandArguments) -> Result<Self> {
Ok(Self {
inputs: arguments.fully_parse_as()?,
})
arguments.fully_parse_or_error(
|input| {
if input.peek(syn::token::Brace) {
Ok(Self {
inputs: EitherErrorInput::Fields(input.parse()?),
})
} else {
Ok(Self {
inputs: EitherErrorInput::JustMessage(
input.parse_with(arguments.full_span_range())?,
),
})
}
},
format!(
"Expected [!error! \"Expected X, found: \" #world] or [!error! {}]",
ErrorInputs::fields_description()
),
)
}

fn execute(self: Box<Self>, interpreter: &mut Interpreter) -> Result<()> {
let message = self.inputs.message.interpret(interpreter)?.value();
let fields = match self.inputs {
EitherErrorInput::Fields(error_inputs) => error_inputs,
EitherErrorInput::JustMessage(stream) => {
let error_message = stream
.interpret_to_new_stream(interpreter)?
.concat_recursive();
return Span::call_site().err(error_message);
}
};

let message = fields.message.interpret(interpreter)?.value();

let error_span = match self.inputs.spans {
let error_span = match fields.spans {
Some(spans) => {
let error_span_stream = spans.interpret_to_new_stream(interpreter)?;

Expand Down
14 changes: 10 additions & 4 deletions src/interpretation/command_field_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,15 @@ macro_rules! define_field_inputs {

impl ArgumentsContent for $inputs_type {
fn error_message() -> String {
format!("Expected: {}", Self::fields_description())
}
}

impl $inputs_type {
fn fields_description() -> String {
use std::fmt::Write;
let mut message = "Expected: {\n".to_string();
let buffer = &mut message;
let mut buffer = String::new();
buffer.write_str("{\n").unwrap();
$(
$(writeln!(buffer, " // {}", $required_description).unwrap();)?
writeln!(buffer, " {}: {},", stringify!($required_field), $required_example).unwrap();
Expand All @@ -107,8 +113,8 @@ macro_rules! define_field_inputs {
$(writeln!(buffer, " // {}", $optional_description).unwrap();)?
writeln!(buffer, " {}?: {},", stringify!($optional_field), $optional_example).unwrap();
)*
buffer.push('}');
message
buffer.write_str("}").unwrap();
buffer
}
}
};
Expand Down
41 changes: 41 additions & 0 deletions src/interpretation/interpreted_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,47 @@ impl InterpretedStream {
pub(crate) fn append_cloned_into(&self, output: &mut InterpretedStream) {
output.token_stream.extend(self.token_stream.clone())
}

pub(crate) fn concat_recursive(self) -> String {
fn concat_recursive_internal(output: &mut String, token_stream: TokenStream) {
for token_tree in token_stream {
match token_tree {
TokenTree::Literal(literal) => match literal.content_if_string_like() {
Some(content) => output.push_str(&content),
None => output.push_str(&literal.to_string()),
},
TokenTree::Group(group) => match group.delimiter() {
Delimiter::Parenthesis => {
output.push('(');
concat_recursive_internal(output, group.stream());
output.push(')');
}
Delimiter::Brace => {
output.push('{');
concat_recursive_internal(output, group.stream());
output.push('}');
}
Delimiter::Bracket => {
output.push('[');
concat_recursive_internal(output, group.stream());
output.push(']');
}
Delimiter::None => {
concat_recursive_internal(output, group.stream());
}
},
TokenTree::Punct(punct) => {
output.push(punct.as_char());
}
TokenTree::Ident(ident) => output.push_str(&ident.to_string()),
}
}
}

let mut output = String::new();
concat_recursive_internal(&mut output, self.into_token_stream());
output
}
}

impl From<TokenTree> for InterpretedStream {
Expand Down
6 changes: 6 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ use crate::internal_prelude::*;

pub(crate) trait IdentExt: Sized {
fn new_bool(value: bool, span: Span) -> Self;
fn with_span(self, span: Span) -> Self;
}

impl IdentExt for Ident {
fn new_bool(value: bool, span: Span) -> Self {
Ident::new(&value.to_string(), span)
}

fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}

pub(crate) trait CursorExt: Sized {
Expand Down
4 changes: 2 additions & 2 deletions tests/compilation_failures/complex/nested.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
error: required fields are missing: message
Occurred whilst parsing [!error! ..] - Expected: {
Occurred whilst parsing [!error! ..] - Expected [!error! "Expected X, found: " #world] or [!error! {
// The error message to display
message: "...",
// An optional [token stream], to determine where to show the error message
spans?: [$abc],
}
}]
--> tests/compilation_failures/complex/nested.rs:7:26
|
7 | [!error! {
Expand Down
5 changes: 5 additions & 0 deletions tests/compilation_failures/core/error_invalid_structure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use preinterpret::*;

fn main() {
preinterpret!([!error! { }]);
}
11 changes: 11 additions & 0 deletions tests/compilation_failures/core/error_invalid_structure.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: required fields are missing: message
Occurred whilst parsing [!error! ..] - Expected [!error! "Expected X, found: " #world] or [!error! {
// The error message to display
message: "...",
// An optional [token stream], to determine where to show the error message
spans?: [$abc],
}]
--> tests/compilation_failures/core/error_invalid_structure.rs:4:28
|
4 | preinterpret!([!error! { }]);
| ^^^
13 changes: 13 additions & 0 deletions tests/compilation_failures/core/error_no_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use preinterpret::*;

macro_rules! assert_literals_eq {
($input1:literal, $input2:literal) => {preinterpret!{
[!if! ($input1 != $input2) {
[!error! "Expected " $input1 " to equal " $input2]
}]
}};
}

fn main() {
assert_literals_eq!(102, 64);
}
15 changes: 15 additions & 0 deletions tests/compilation_failures/core/error_no_fields.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: Expected 102 to equal 64
--> tests/compilation_failures/core/error_no_fields.rs:4:44
|
4 | ($input1:literal, $input2:literal) => {preinterpret!{
| ____________________________________________^
5 | | [!if! ($input1 != $input2) {
6 | | [!error! "Expected " $input1 " to equal " $input2]
7 | | }]
8 | | }};
| |_____^
...
12 | assert_literals_eq!(102, 64);
| ---------------------------- in this macro invocation
|
= note: this error originates in the macro `preinterpret` which comes from the expansion of the macro `assert_literals_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

0 comments on commit b874009

Please sign in to comment.