Skip to content

Commit

Permalink
WIP: Add while and assign commands
Browse files Browse the repository at this point in the history
  • Loading branch information
dhedey committed Jan 2, 2025
1 parent b57a688 commit befb78d
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 48 deletions.
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@
* `[!increment! ...]`
* Control flow commands:
* `[!if! COND { ... }]` and `[!if! COND { ... } !else! { ... }]`
* `[!while! cond {}]`
* Token-stream utility commands:
* `[!empty!]`
* `[!is_empty! #stream]`
* `[!length! #stream]` which gives the number of token trees in the token stream.
* `[!group! ...]` which wraps the tokens in a transparent group. Useful with `!for!`.
* Disallow `[!let! #x =]` and require `[!let! #x = [!empty!]]` (give a good error message).

### To come

* Use `[!let! #x = 12]` instead of `[!set! ..]`.
* Disallow `[!let! #x =]` and require `[!let! #x = [!empty!]]` (give a good error message).
* `[!while! cond {}]`
* `[!for! #x in [#y] {}]`... and make it so that whether commands or variable substitutions get replaced by groups depends on the interpreter context (likely only expressions should use groups)
* ? Use `[!let! #x = 12]` instead of `[!set! ..]`.
* `[!for! #x in [#y] {}]`
* `[!range! 0..5]`
* `[!error! "message" token stream for span]`
* Remove `[!increment! ...]` and replace with `[!assign! #x += 1]`
* Reconfiguring iteration limit
* Support `!else if!` in `!if!`
* Token stream manipulation... and make it performant
* Extend token stream
* Token stream manipulation... and make it performant (maybe by storing either TokenStream for extension; or `ParseStream` for consumption)
* Extend token stream `[!extend! #x += ...]`
* Consume from start of token stream
* Support string & char literals (for comparisons & casts) in expressions
* Add more tests
Expand Down
75 changes: 57 additions & 18 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ macro_rules! define_commands {
}
) => {
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy)]
pub(crate) enum $enum_name {
$(
$command,
Expand Down Expand Up @@ -50,6 +51,7 @@ macro_rules! define_commands {
}
pub(crate) use define_commands;

#[derive(Clone)]
pub(crate) struct CommandInvocation {
command_kind: CommandKind,
command: Command,
Expand Down Expand Up @@ -79,6 +81,7 @@ impl HasSpanRange for CommandInvocation {
}
}

#[derive(Clone)]
pub(crate) struct Variable {
marker: Punct, // #
variable_name: Ident,
Expand All @@ -92,28 +95,63 @@ impl Variable {
}
}

pub(crate) fn variable_name(&self) -> &Ident {
&self.variable_name
pub(crate) fn variable_name(&self) -> String {
self.variable_name.to_string()
}

pub(crate) fn execute_substitution(
pub(crate) fn set<'i>(
&self,
interpreter: &mut Interpreter,
) -> Result<TokenStream> {
let Variable { variable_name, .. } = self;
match interpreter.get_variable(&variable_name.to_string()) {
Some(variable_value) => Ok(variable_value.clone()),
None => {
self.span_range().err(
format!(
"The variable {} wasn't set.\nIf this wasn't intended to be a variable, work around this with [!raw! {}]",
self,
self,
),
)
}
interpreter: &'i mut Interpreter,
value: TokenStream,
) {
interpreter.set_variable(self.variable_name(), value);
}

pub(crate) fn read_substitution<'i>(
&self,
interpreter: &'i Interpreter,
) -> Result<&'i TokenStream> {
self.read_or_else(
interpreter,
|| format!(
"The variable {} wasn't set.\nIf this wasn't intended to be a variable, work around this with [!raw! {}]",
self,
self,
)
)
}

pub(crate) fn read_required<'i>(
&self,
interpreter: &'i Interpreter,
) -> Result<&'i TokenStream> {
self.read_or_else(
interpreter,
|| format!(
"The variable {} wasn't set.",
self,
)
)
}

pub(crate) fn read_or_else<'i>(
&self,
interpreter: &'i Interpreter,
create_error: impl FnOnce() -> String,
) -> Result<&'i TokenStream> {
match self.read_option(interpreter) {
Some(token_stream) => Ok(token_stream),
None => self.span_range().err(create_error()),
}
}

fn read_option<'i>(
&self,
interpreter: &'i Interpreter,
) -> Option<&'i TokenStream> {
let Variable { variable_name, .. } = self;
interpreter.get_variable(&variable_name.to_string())
}
}

impl HasSpanRange for Variable {
Expand All @@ -128,6 +166,7 @@ impl core::fmt::Display for Variable {
}
}

#[derive(Clone)]
pub(crate) struct Command {
command_ident: Ident,
command_span: Span,
Expand Down Expand Up @@ -156,7 +195,7 @@ impl Command {
self.command_span.error(message)
}

pub(crate) fn err(&self, message: impl core::fmt::Display) -> Result<TokenStream> {
pub(crate) fn err<T>(&self, message: impl core::fmt::Display) -> Result<T> {
Err(self.error(message))
}

Expand Down
56 changes: 56 additions & 0 deletions src/commands/control_flow_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,59 @@ fn parse_if_statement(tokens: &mut Tokens) -> Option<IfStatement> {
false_code,
})
}

pub(crate) struct WhileCommand;

impl CommandDefinition for WhileCommand {
const COMMAND_NAME: &'static str = "while";

fn execute(interpreter: &mut Interpreter, mut command: Command) -> Result<TokenStream> {
let parsed = match parse_while_statement(command.argument_tokens()) {
Some(parsed) => parsed,
None => {
return command.err("Expected [!while! (condition) { code }]");
}
};

let mut output = TokenStream::new();
let mut iteration_count = 0;
loop {
let interpreted_condition =
interpreter.interpret_item(parsed.condition.clone(), SubstitutionMode::expression())?;
let evaluated_condition = evaluate_expression(
interpreted_condition,
ExpressionParsingMode::BeforeCurlyBraces,
)?
.expect_bool("An if condition must evaluate to a boolean")?
.value();

iteration_count += 1;
if !evaluated_condition {
break;
}
interpreter.config().check_iteration_count(&command, iteration_count)?;
interpreter.interpret_token_stream_into(
parsed.code.clone(),
SubstitutionMode::token_stream(),
&mut output,
)?;
}

Ok(output)
}
}

struct WhileStatement {
condition: NextItem,
code: TokenStream,
}

fn parse_while_statement(tokens: &mut Tokens) -> Option<WhileStatement> {
let condition = tokens.next_item().ok()??;
let code = tokens.next_as_kinded_group(Delimiter::Brace)?.stream();
tokens.check_end()?;
Some(WhileStatement {
condition,
code,
})
}
64 changes: 62 additions & 2 deletions src/commands/expression_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,66 @@ impl CommandDefinition for EvaluateCommand {
}
}

pub(crate) struct AssignCommand;

impl CommandDefinition for AssignCommand {
const COMMAND_NAME: &'static str = "assign";

fn execute(interpreter: &mut Interpreter, mut command: Command) -> Result<TokenStream> {
let AssignStatementStart {
variable,
operator,
} = AssignStatementStart::parse(command.argument_tokens())
.ok_or_else(|| command.error("Expected [!assign! #variable += ...] for + or some other operator supported in an expression"))?;

let mut expression_tokens = TokenStream::new();
expression_tokens.push_new_group(
// TODO: Replace with `variable.read_into(tokens, substitution_mode)`
// TODO: Replace most methods on interpeter with e.g.
// command.interpret_into, next_item.interpet_into, etc.
// And also create an Expression struct
// TODO: Fix Expression to not need different parsing modes,
// and to be parsed from the full token stream or until braces { .. }
// or as a single item
variable.read_required(interpreter)?.clone(),
Delimiter::None,
variable.span_range(),
);
expression_tokens.push_token_tree(operator.into());
expression_tokens.push_new_group(
command.interpret_remaining_arguments(interpreter, SubstitutionMode::expression())?,
Delimiter::None,
command.span_range(),
);

let output = evaluate_expression(expression_tokens, ExpressionParsingMode::Standard)?.into_token_stream();
variable.set(interpreter, output);

Ok(TokenStream::new())
}
}

struct AssignStatementStart {
variable: Variable,
operator: Punct,
}

impl AssignStatementStart {
fn parse(tokens: &mut Tokens) -> Option<Self> {
let variable = tokens.next_item_as_variable("").ok()?;
let operator = tokens.next_as_punct()?;
match operator.as_char() {
'+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' => {}
_ => return None,
}
tokens.next_as_punct_matching('=')?;
Some(AssignStatementStart {
variable,
operator,
})
}
}

pub(crate) struct IncrementCommand;

impl CommandDefinition for IncrementCommand {
Expand All @@ -23,9 +83,9 @@ impl CommandDefinition for IncrementCommand {
.argument_tokens()
.next_item_as_variable(error_message)?;
command.argument_tokens().assert_end(error_message)?;
let variable_contents = variable.execute_substitution(interpreter)?;
let variable_contents = variable.read_substitution(interpreter)?;
let evaluated_integer =
evaluate_expression(variable_contents, ExpressionParsingMode::Standard)?
evaluate_expression(variable_contents.clone(), ExpressionParsingMode::Standard)?
.expect_integer(&format!("Expected {variable} to evaluate to an integer"))?;
interpreter.set_variable(
variable.variable_name().to_string(),
Expand Down
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ define_commands! {

// Expression Commands
EvaluateCommand,
AssignCommand,
IncrementCommand,

// Control flow commands
IfCommand,
WhileCommand,

// Token Commands
EmptyCommand,
Expand Down
4 changes: 2 additions & 2 deletions src/commands/token_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ impl CommandDefinition for GroupCommand {
fn execute(interpreter: &mut Interpreter, mut command: Command) -> Result<TokenStream> {
let mut output = TokenStream::new();
output.push_new_group(
command.span_range(),
Delimiter::None,
command.interpret_remaining_arguments(interpreter, SubstitutionMode::token_stream())?,
Delimiter::None,
command.span_range(),
);
Ok(output)
}
Expand Down
16 changes: 10 additions & 6 deletions src/internal_prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,26 @@ pub(crate) use crate::string_conversion::*;

pub(crate) trait TokenTreeExt: Sized {
fn bool(value: bool, span: Span) -> Self;
fn group(tokens: TokenStream, delimeter: Delimiter, span: Span) -> Self;
}

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

fn group(inner_tokens: TokenStream, delimeter: Delimiter, span: Span) -> Self {
TokenTree::Group(Group::new(delimeter, inner_tokens).with_span(span))
}
}

pub(crate) trait TokenStreamExt: Sized {
fn push_token_tree(&mut self, token_tree: TokenTree);
fn push_new_group(
&mut self,
span_range: SpanRange,
delimiter: Delimiter,
inner_tokens: TokenStream,
delimiter: Delimiter,
span_range: SpanRange,
);
}

Expand All @@ -38,12 +43,11 @@ impl TokenStreamExt for TokenStream {

fn push_new_group(
&mut self,
span_range: SpanRange,
delimiter: Delimiter,
inner_tokens: TokenStream,
delimiter: Delimiter,
span_range: SpanRange,
) {
let group = Group::new(delimiter, inner_tokens).with_span(span_range.span());
self.push_token_tree(TokenTree::Group(group));
self.push_token_tree(TokenTree::group(inner_tokens, delimiter, span_range.span()));
}
}

Expand Down
Loading

0 comments on commit befb78d

Please sign in to comment.