use crate::ast::{
IfClause, ReasoningAction, ReasoningActionTarget, ReasoningBlock, Reference, RunClause,
SetClause, Spanned, WithClause, WithValue,
};
use crate::lexer::Token;
use chumsky::prelude::*;
use super::expressions::{expr, reference};
use super::instructions::any_instructions;
use super::primitives::{
dedent, description_entry, ident, indent, newline, skip_block_noise, spanned_ident, string_lit,
to_ast_span, ParserInput, Span,
};
pub(crate) fn reasoning_action_target_parser<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<ReasoningActionTarget>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
choice((
just(Token::At)
.ignore_then(ident().filter(|s| *s == "utils"))
.ignore_then(just(Token::Dot))
.ignore_then(just(Token::Transition))
.ignore_then(just(Token::To))
.ignore_then(reference())
.map_with(|target, e| {
Spanned::new(ReasoningActionTarget::TransitionTo(target), to_ast_span(e.span()))
}),
just(Token::At)
.ignore_then(ident().filter(|s| *s == "utils"))
.ignore_then(just(Token::Dot))
.ignore_then(ident().filter(|s| *s == "escalate"))
.map_with(|_, e| Spanned::new(ReasoningActionTarget::Escalate, to_ast_span(e.span()))),
just(Token::At)
.ignore_then(ident().filter(|s| *s == "utils"))
.ignore_then(just(Token::Dot))
.ignore_then(ident().filter(|s| *s == "setVariables"))
.map_with(|_, e| {
Spanned::new(ReasoningActionTarget::SetVariables, to_ast_span(e.span()))
}),
just(Token::At)
.ignore_then(just(Token::Topic))
.ignore_then(just(Token::Dot))
.ignore_then(ident())
.map_with(|name, e| {
let r = Reference::new("topic", vec![name.to_string()]);
Spanned::new(ReasoningActionTarget::TopicDelegate(r), to_ast_span(e.span()))
}),
reference()
.filter(|r| r.namespace == "actions")
.map_with(|r, e| Spanned::new(ReasoningActionTarget::Action(r), to_ast_span(e.span()))),
))
}
pub(crate) fn with_clause<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<WithClause>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
let param_name = choice((
ident().map(|s| s.to_string()),
string_lit().map(|s| s.to_string()),
just(Token::Description).to("description".to_string()),
just(Token::Id).to("id".to_string()),
));
just(Token::With)
.ignore_then(param_name)
.then_ignore(just(Token::Assign))
.then(expr().map(|e| WithValue::Expr(e.node)))
.map_with(|(param, value), e| {
Spanned::new(
WithClause {
param: Spanned::new(param.to_string(), to_ast_span(e.span())),
value: Spanned::new(value, to_ast_span(e.span())),
},
to_ast_span(e.span()),
)
})
}
pub(crate) fn set_clause<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<SetClause>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
just(Token::Set)
.ignore_then(reference())
.then_ignore(just(Token::Assign))
.then(expr())
.map_with(|(target, source), e| {
Spanned::new(
SetClause {
target: Spanned::new(target, to_ast_span(e.span())),
source,
},
to_ast_span(e.span()),
)
})
}
#[derive(Clone)]
pub(crate) enum RunClauseEntry {
With(Spanned<WithClause>),
Set(Spanned<SetClause>),
}
fn run_clause_nested_block<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Vec<RunClauseEntry>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
choice((with_clause().map(RunClauseEntry::With), set_clause().map(RunClauseEntry::Set)))
.separated_by(skip_block_noise())
.allow_trailing()
.collect::<Vec<_>>()
}
pub(crate) fn run_clause<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<RunClause>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
let nested_block = newline()
.ignore_then(indent())
.ignore_then(run_clause_nested_block())
.then_ignore(skip_block_noise())
.then_ignore(dedent())
.or_not()
.map(|opt| opt.unwrap_or_default())
.boxed();
just(Token::Run)
.ignore_then(reference())
.then(nested_block)
.map_with(|(action, entries), e| {
let mut with_clauses = Vec::new();
let mut set_clauses = Vec::new();
for entry in entries {
match entry {
RunClauseEntry::With(w) => with_clauses.push(w),
RunClauseEntry::Set(s) => set_clauses.push(s),
}
}
Spanned::new(
RunClause {
action: Spanned::new(action, to_ast_span(e.span())),
with_clauses,
set_clauses,
},
to_ast_span(e.span()),
)
})
}
#[derive(Clone)]
enum ReasoningActionEntry {
Description(Spanned<String>),
With(Spanned<WithClause>),
Set(Spanned<SetClause>),
AvailableWhen(Spanned<crate::ast::Expr>),
Run(Spanned<RunClause>),
Transition(Spanned<Reference>),
If(Spanned<IfClause>),
}
pub(crate) fn reasoning_action<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<ReasoningAction>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
spanned_ident()
.then_ignore(just(Token::Colon))
.then(reasoning_action_target_parser())
.labelled("reasoning action")
.then(
newline()
.ignore_then(indent())
.ignore_then(
choice((
description_entry().map(ReasoningActionEntry::Description),
with_clause().map(ReasoningActionEntry::With),
set_clause().map(ReasoningActionEntry::Set),
just(Token::Available)
.ignore_then(just(Token::When))
.ignore_then(expr())
.map(ReasoningActionEntry::AvailableWhen),
run_clause().map(ReasoningActionEntry::Run),
just(Token::If)
.ignore_then(expr())
.then_ignore(just(Token::Colon))
.then(
newline()
.ignore_then(indent())
.ignore_then(
just(Token::Transition)
.ignore_then(just(Token::To))
.ignore_then(reference())
.map_with(|target, e| {
Some(Spanned::new(target, to_ast_span(e.span())))
}),
)
.then_ignore(skip_block_noise())
.then_ignore(dedent()),
)
.map_with(|(condition, transition), e| {
ReasoningActionEntry::If(Spanned::new(
IfClause {
condition,
transition,
},
to_ast_span(e.span()),
))
}),
just(Token::Transition)
.ignore_then(just(Token::To))
.ignore_then(reference())
.map_with(|target, e| {
ReasoningActionEntry::Transition(Spanned::new(
target,
to_ast_span(e.span()),
))
}),
))
.separated_by(skip_block_noise())
.allow_trailing()
.collect::<Vec<_>>(),
)
.then_ignore(skip_block_noise())
.then_ignore(dedent())
.or_not()
.map(|opt| opt.unwrap_or_default()),
)
.map_with(|((name, target), entries), e| {
let mut action = ReasoningAction {
name,
target,
description: None,
available_when: None,
with_clauses: Vec::new(),
set_clauses: Vec::new(),
run_clauses: Vec::new(),
if_clauses: Vec::new(),
transition: None,
};
for entry in entries {
match entry {
ReasoningActionEntry::Description(d) => action.description = Some(d),
ReasoningActionEntry::With(w) => action.with_clauses.push(w),
ReasoningActionEntry::Set(s) => action.set_clauses.push(s),
ReasoningActionEntry::AvailableWhen(e) => action.available_when = Some(e),
ReasoningActionEntry::Run(r) => action.run_clauses.push(r),
ReasoningActionEntry::Transition(t) => action.transition = Some(t),
ReasoningActionEntry::If(i) => action.if_clauses.push(i),
}
}
Spanned::new(action, to_ast_span(e.span()))
})
}
#[derive(Clone)]
enum ReasoningEntry {
Instructions(Spanned<crate::ast::Instructions>),
Actions(Vec<Spanned<ReasoningAction>>),
}
pub(crate) fn reasoning_block<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<ReasoningBlock>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
just(Token::Reasoning)
.ignore_then(just(Token::Colon))
.ignore_then(newline())
.ignore_then(skip_block_noise())
.ignore_then(indent())
.ignore_then(
choice((
any_instructions().map(ReasoningEntry::Instructions),
just(Token::Actions)
.map_with(|_, e| e.span()) .then_ignore(just(Token::Colon))
.then_ignore(newline())
.then_ignore(skip_block_noise())
.then(
just(Token::Dedent).rewind().to(None).or(indent()
.ignore_then(
reasoning_action()
.separated_by(skip_block_noise())
.allow_trailing()
.collect::<Vec<_>>(),
)
.then_ignore(skip_block_noise())
.then_ignore(dedent())
.map(Some)),
)
.validate(|(actions_span, entries), _, emitter| {
if entries.is_none() {
emitter.emit(Rich::custom(
actions_span,
"reasoning actions block cannot be empty",
));
}
entries.unwrap_or_default()
})
.labelled("reasoning actions")
.map(ReasoningEntry::Actions),
))
.separated_by(skip_block_noise())
.allow_trailing()
.collect::<Vec<_>>(),
)
.then_ignore(skip_block_noise())
.then_ignore(dedent())
.labelled("reasoning block")
.map_with(|entries, e| {
let mut block = ReasoningBlock {
instructions: None,
actions: None,
};
for entry in entries {
match entry {
ReasoningEntry::Instructions(i) => block.instructions = Some(i),
ReasoningEntry::Actions(a) => {
block.actions = Some(Spanned::new(a, to_ast_span(e.span())))
}
}
}
Spanned::new(block, to_ast_span(e.span()))
})
}