use super::{
ast::conditional::{BinOp, BinOpTy, Expr, ExprTy, PreOp, PreOpTy},
ast::Ident,
Macro, SyntaxModifiers, SyntaxToken, SyntaxType,
};
use crate::{
diag::{ExprDiag, PreprocDefineDiag, Semantic, Syntax},
lexer::preprocessor::ConditionToken,
Either, Span, SpanEncoding, Spanned,
};
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Write,
};
pub(super) fn cond_parser(
tokens: Vec<Spanned<ConditionToken>>,
macros: &HashMap<String, (Span, Macro)>,
span_encoding: SpanEncoding,
) -> (Option<Expr>, Vec<Syntax>, Vec<SyntaxToken>) {
let mut walker = Walker::new(tokens, macros, span_encoding);
let mut parser = ShuntingYard {
stack: VecDeque::new(),
operators: VecDeque::new(),
groups: Vec::new(),
};
parser.parse(&mut walker);
(
parser.create_ast(),
walker.syntax_diags,
walker.syntax_tokens,
)
}
struct Walker<'a> {
streams: Vec<(String, Vec<Spanned<ConditionToken>>, usize)>,
macros: &'a HashMap<String, (Span, Macro)>,
macro_call_site: Option<Span>,
active_macros: HashSet<String>,
disable_macro_expansion: bool,
syntax_diags: Vec<Syntax>,
semantic_diags: Vec<Semantic>,
syntax_tokens: Vec<SyntaxToken>,
span_encoding: SpanEncoding,
}
impl<'a> Walker<'a> {
fn new(
tokens: Vec<Spanned<ConditionToken>>,
macros: &'a HashMap<String, (Span, Macro)>,
span_encoding: SpanEncoding,
) -> Self {
let streams = if !tokens.is_empty() {
vec![("".into(), tokens, 0)]
} else {
vec![]
};
let mut active_macros = HashSet::new();
active_macros.insert("".into());
let mut walker = Self {
streams,
macros,
macro_call_site: None,
active_macros,
disable_macro_expansion: false,
syntax_diags: Vec::new(),
semantic_diags: Vec::new(),
syntax_tokens: Vec::new(),
span_encoding,
};
walker._advance(true);
walker
}
fn peek(&self) -> Option<Spanned<&ConditionToken>> {
if self.streams.is_empty() {
None
} else if self.streams.len() == 1 {
let (_, stream, cursor) = self.streams.last().unwrap();
stream.get(*cursor).map(|(t, s)| (t, *s))
} else {
let (_, stream, cursor) = self.streams.last().unwrap();
stream
.get(*cursor)
.map(|(t, _)| (t, self.macro_call_site.unwrap()))
}
}
fn advance(&mut self) {
self._advance(false);
}
fn _advance(&mut self, mut dont_increment: bool) {
'outer: while let Some((identifier, stream, cursor)) =
self.streams.last_mut()
{
if !dont_increment {
*cursor += 1;
}
dont_increment = false;
if *cursor == stream.len() {
let ident = identifier.clone();
self.active_macros.remove(&ident);
self.streams.remove(self.streams.len() - 1);
continue;
}
let (token, token_span) = stream.get(*cursor).unwrap();
match token {
ConditionToken::Ident(s) if !self.disable_macro_expansion => {
if let Some((signature_span, macro_)) = self.macros.get(s) {
if self.active_macros.contains(s) {
break;
}
let ident_span = *token_span;
if let Macro::Function { params, body } = macro_ {
let mut tmp_cursor = *cursor + 1;
let mut syntax_spans = vec![SyntaxToken {
ty: SyntaxType::FunctionMacro,
modifiers: SyntaxModifiers::empty(),
span: ident_span,
}];
loop {
match stream.get(tmp_cursor) {
Some((token, token_span)) => match token {
ConditionToken::LineComment(_)
| ConditionToken::BlockComment {
..
} => {
syntax_spans.push(SyntaxToken {
ty: SyntaxType::Comment,
modifiers:
SyntaxModifiers::empty(),
span: *token_span,
});
tmp_cursor += 1;
}
_ => break,
},
None => break 'outer,
}
}
let l_paren_span = match stream.get(tmp_cursor) {
Some((token, token_span)) => match token {
ConditionToken::LParen => {
syntax_spans.push(SyntaxToken {
ty: SyntaxType::Punctuation,
modifiers: SyntaxModifiers::empty(),
span: *token_span,
});
*cursor = tmp_cursor + 1;
*token_span
}
_ => {
break;
}
},
None => break,
};
let mut prev_span = l_paren_span;
let mut paren_groups = 0;
let mut args = Vec::new();
let mut arg = Vec::new();
let mut first_token = true;
let r_paren_span = loop {
let (token, token_span) = match stream
.get(*cursor)
{
Some(t) => t,
None => {
self.syntax_diags.push(Syntax::PreprocDefine(PreprocDefineDiag::ParamsExpectedRParen(
prev_span.next_single_width()
)));
break 'outer;
}
};
match token {
ConditionToken::Comma => {
syntax_spans.push(SyntaxToken {
ty: SyntaxType::Punctuation,
modifiers: SyntaxModifiers::empty(),
span: *token_span,
});
if paren_groups == 0 {
let arg = std::mem::take(&mut arg);
args.push(arg);
}
prev_span = *token_span;
*cursor += 1;
continue;
}
ConditionToken::LParen => {
paren_groups += 1;
}
ConditionToken::RParen => {
if paren_groups == 0 {
syntax_spans.push(SyntaxToken {
ty: SyntaxType::Punctuation,
modifiers:
SyntaxModifiers::empty(),
span: *token_span,
});
if !first_token {
let arg =
std::mem::take(&mut arg);
args.push(arg);
}
break *token_span;
}
paren_groups -= 1;
}
_ => {}
}
syntax_spans.push(SyntaxToken {
ty: token.non_semantic_colour(),
modifiers: SyntaxModifiers::empty(),
span: *token_span,
});
arg.push((token.clone(), *token_span));
*cursor += 1;
first_token = false;
};
let call_site_span =
Span::new(ident_span.start, r_paren_span.end);
if params.len() != args.len() {
self.semantic_diags.push(
Semantic::FunctionMacroMismatchedArgCount(
call_site_span,
*signature_span,
),
);
continue;
}
let mut param_map = HashMap::new();
params.iter().zip(args.into_iter()).for_each(
|(ident, tokens)| {
param_map.insert(&ident.name, tokens);
},
);
let mut new_body = Vec::with_capacity(body.len());
for (token, token_span) in body {
match &token {
crate::lexer::Token::Ident(str) => {
if let Some(arg) = param_map.get(&str) {
for token in arg {
new_body.push(ConditionToken::to_normal_token(token.clone()));
}
continue;
}
}
_ => {}
}
new_body.push((token.clone(), *token_span));
}
let (new_body, mut syntax, mut semantic) =
crate::lexer::preprocessor::concat_macro_body(
new_body,
self.span_encoding,
);
self.syntax_diags.append(&mut syntax);
self.semantic_diags.append(&mut semantic);
if body.is_empty() {
let ident = s.to_owned();
if self.streams.len() == 1 {
self.macro_call_site = Some(ident_span);
self.syntax_tokens
.append(&mut syntax_spans);
}
self.active_macros.insert(ident.clone());
self.streams.push((
ident,
vec![(
ConditionToken::Num(1),
signature_span.end_zero_width(),
)],
0,
));
dont_increment = true;
continue;
}
let ident = s.to_owned();
if self.streams.len() == 1 {
self.macro_call_site = Some(call_site_span);
self.syntax_tokens.append(&mut syntax_spans);
}
self.active_macros.insert(ident.clone());
let mut s = String::with_capacity(50);
for (token, _) in new_body {
write!(s, "{token}").unwrap();
}
use crate::lexer::{self, Lexer, Utf16};
let mut lexer: Lexer<Utf16> =
Lexer::new(&s, self.span_encoding);
let new_tokens =
lexer::preprocessor::parse_condition_tokens(
&mut lexer,
);
self.streams.push((ident, new_tokens, 0));
dont_increment = true;
continue;
} else if let Macro::Object(stream) = macro_ {
if stream.is_empty() {
let ident = s.to_owned();
if self.streams.len() == 1 {
self.macro_call_site = Some(ident_span);
self.syntax_tokens.push(SyntaxToken {
ty: SyntaxType::ObjectMacro,
modifiers: SyntaxModifiers::CONDITIONAL,
span: ident_span,
});
}
self.active_macros.insert(ident.clone());
self.streams.push((
ident,
vec![(
ConditionToken::Num(1),
signature_span.end_zero_width(),
)],
0,
));
dont_increment = true;
continue;
}
let ident = s.to_owned();
if self.streams.len() == 1 {
self.macro_call_site = Some(ident_span);
self.syntax_tokens.push(SyntaxToken {
ty: SyntaxType::ObjectMacro,
modifiers: SyntaxModifiers::CONDITIONAL,
span: ident_span,
});
}
self.active_macros.insert(ident.clone());
let mut s = String::with_capacity(50);
for (token, _) in stream {
write!(s, "{token}").unwrap();
}
use crate::lexer::{self, Lexer, Utf16};
let mut lexer: Lexer<Utf16> =
Lexer::new(&s, self.span_encoding);
let new_tokens =
lexer::preprocessor::parse_condition_tokens(
&mut lexer,
);
self.streams.push((ident, new_tokens, 0));
dont_increment = true;
continue;
}
} else {
break;
}
}
ConditionToken::LineComment(_) => {
let token_span = *token_span;
if self.streams.len() == 1 {
self.syntax_tokens.push(SyntaxToken {
ty: SyntaxType::Comment,
modifiers: SyntaxModifiers::empty(),
span: token_span,
});
}
}
ConditionToken::BlockComment { contains_eof, .. } => {
if *contains_eof {
self.syntax_diags.push(Syntax::BlockCommentMissingEnd(
token_span.end_zero_width(),
));
}
let token_span = *token_span;
if self.streams.len() == 1 {
self.syntax_tokens.push(SyntaxToken {
ty: SyntaxType::Comment,
modifiers: SyntaxModifiers::empty(),
span: token_span,
});
}
}
_ => break,
}
}
if self.streams.len() <= 1 {
self.macro_call_site = None;
}
}
fn is_done(&self) -> bool {
self.streams.is_empty()
}
fn push_colour(&mut self, span: Span, token: SyntaxType) {
self.syntax_tokens.push(SyntaxToken {
ty: token,
modifiers: SyntaxModifiers::CONDITIONAL,
span,
});
}
}
#[derive(Debug, Clone, PartialEq)]
struct Node {
ty: NodeTy,
span: Span,
}
#[derive(Debug, Clone, PartialEq)]
enum NodeTy {
Num(usize),
Defined(Ident),
}
#[derive(Debug, Clone, PartialEq)]
struct Op {
ty: OpTy,
span: Span,
}
#[derive(Debug, Clone, PartialEq)]
enum OpTy {
Add(bool),
Sub(bool),
Mul(bool),
Div(bool),
Rem(bool),
And(bool),
Or(bool),
Xor(bool),
LShift(bool),
RShift(bool),
EqEq(bool),
NotEq(bool),
AndAnd(bool),
OrOr(bool),
Gt(bool),
Lt(bool),
Ge(bool),
Le(bool),
Neg(bool),
Flip(bool),
Not(bool),
ParenStart,
Paren(bool, Span, Span),
}
impl Op {
fn from_token(token: ConditionToken, span: Span) -> Self {
Self {
span,
ty: match token {
ConditionToken::Add => OpTy::Add(false),
ConditionToken::Sub => OpTy::Sub(false),
ConditionToken::Mul => OpTy::Mul(false),
ConditionToken::Div => OpTy::Div(false),
ConditionToken::Rem => OpTy::Rem(false),
ConditionToken::And => OpTy::And(false),
ConditionToken::Or => OpTy::Or(false),
ConditionToken::Xor => OpTy::Xor(false),
ConditionToken::LShift => OpTy::LShift(false),
ConditionToken::RShift => OpTy::RShift(false),
ConditionToken::EqEq => OpTy::EqEq(false),
ConditionToken::NotEq => OpTy::NotEq(false),
ConditionToken::AndAnd => OpTy::AndAnd(false),
ConditionToken::OrOr => OpTy::OrOr(false),
ConditionToken::Gt => OpTy::Gt(false),
ConditionToken::Lt => OpTy::Lt(false),
ConditionToken::Ge => OpTy::Ge(false),
ConditionToken::Le => OpTy::Le(false),
ConditionToken::Not | ConditionToken::Flip | _ => {
unreachable!("Given a conditional `token` which should never be handled by this function.")
}
},
}
}
#[rustfmt::skip]
fn precedence(&self) -> usize {
match &self.ty {
OpTy::Neg(_)
| OpTy::Flip(_)
| OpTy::Not(_) => 29,
OpTy::Mul(_) | OpTy::Div(_) | OpTy::Rem(_) => 27,
OpTy::Add(_) | OpTy::Sub(_) => 25,
OpTy::LShift(_) | OpTy::RShift(_) => 23,
OpTy::Lt(_) | OpTy::Gt(_) | OpTy::Le(_) | OpTy::Ge(_) => 21,
OpTy::EqEq(_) | OpTy::NotEq(_) => 19,
OpTy::And(_) => 17,
OpTy::Xor(_) => 15,
OpTy::Or(_) => 13,
OpTy::AndAnd(_) => 11,
OpTy::OrOr(_) => 9,
_ => unreachable!("The conditional expression operator {self:?} does not have a precedence value because it should never be passed into this function. Something has gone wrong!"),
}
}
fn is_bin(&self) -> bool {
match &self.ty {
OpTy::Add(_)
| OpTy::Sub(_)
| OpTy::Mul(_)
| OpTy::Div(_)
| OpTy::Rem(_)
| OpTy::And(_)
| OpTy::Or(_)
| OpTy::Xor(_)
| OpTy::LShift(_)
| OpTy::RShift(_)
| OpTy::EqEq(_)
| OpTy::NotEq(_)
| OpTy::AndAnd(_)
| OpTy::OrOr(_)
| OpTy::Gt(_)
| OpTy::Lt(_)
| OpTy::Ge(_)
| OpTy::Le(_) => true,
OpTy::Neg(_) | OpTy::Flip(_) | OpTy::Not(_) => false,
OpTy::ParenStart => false,
OpTy::Paren(_, _, _) => false,
}
}
fn to_bin_op(self) -> BinOp {
let ty = match self.ty {
OpTy::Add(_) => BinOpTy::Add,
OpTy::Sub(_) => BinOpTy::Sub,
OpTy::Mul(_) => BinOpTy::Mul,
OpTy::Div(_) => BinOpTy::Div,
OpTy::Rem(_) => BinOpTy::Rem,
OpTy::And(_) => BinOpTy::And,
OpTy::Or(_) => BinOpTy::Or,
OpTy::Xor(_) => BinOpTy::Xor,
OpTy::LShift(_) => BinOpTy::LShift,
OpTy::RShift(_) => BinOpTy::RShift,
OpTy::EqEq(_) => BinOpTy::EqEq,
OpTy::NotEq(_) => BinOpTy::NotEq,
OpTy::AndAnd(_) => BinOpTy::AndAnd,
OpTy::OrOr(_) => BinOpTy::OrOr,
OpTy::Gt(_) => BinOpTy::Gt,
OpTy::Lt(_) => BinOpTy::Lt,
OpTy::Ge(_) => BinOpTy::Ge,
OpTy::Le(_) => BinOpTy::Le,
_ => unreachable!("Given a conditional expression `ty` which should not be handled here.")
};
BinOp {
span: self.span,
ty,
}
}
}
enum Group {
Paren(bool, Span),
}
struct ShuntingYard {
stack: VecDeque<Either<Node, Op>>,
operators: VecDeque<Op>,
groups: Vec<Group>,
}
impl ShuntingYard {
fn push_operator(&mut self, op: Op) {
while self.operators.back().is_some() {
let back = self.operators.back().unwrap();
match back.ty {
OpTy::ParenStart => break,
_ => {}
}
if op.precedence() == back.precedence()
&& op.is_bin() && back.is_bin()
{
let moved = self.operators.pop_back().unwrap();
self.stack.push_back(Either::Right(moved));
} else if op.precedence() < back.precedence() {
let moved = self.operators.pop_back().unwrap();
self.stack.push_back(Either::Right(moved));
} else {
break;
}
}
self.operators.push_back(op);
}
fn end_paren(&mut self, end_span: Span) -> Result<(), ()> {
let group = if !self.groups.is_empty() {
self.groups.remove(self.groups.len() - 1)
} else {
return Err(());
};
match group {
Group::Paren(has_inner, l_paren) => {
while self.operators.back().is_some() {
let op = self.operators.pop_back().unwrap();
if let OpTy::ParenStart = op.ty {
self.stack.push_back(Either::Right(Op {
span: Span::new(l_paren.start, end_span.end),
ty: OpTy::Paren(has_inner, l_paren, end_span),
}));
break;
} else {
self.stack.push_back(Either::Right(op));
}
}
}
}
Ok(())
}
fn set_op_rhs_toggle(&mut self) {
if let Some(op) = self.operators.back_mut() {
match &mut op.ty {
OpTy::Add(b)
| OpTy::Sub(b)
| OpTy::Mul(b)
| OpTy::Div(b)
| OpTy::Rem(b)
| OpTy::And(b)
| OpTy::Or(b)
| OpTy::Xor(b)
| OpTy::LShift(b)
| OpTy::RShift(b)
| OpTy::EqEq(b)
| OpTy::NotEq(b)
| OpTy::AndAnd(b)
| OpTy::OrOr(b)
| OpTy::Gt(b)
| OpTy::Lt(b)
| OpTy::Ge(b)
| OpTy::Le(b)
| OpTy::Neg(b)
| OpTy::Flip(b)
| OpTy::Not(b) => *b = true,
_ => {}
}
}
if let Some(Group::Paren(b, _)) = self.groups.last_mut() {
*b = true;
}
}
fn just_started_paren(&self) -> bool {
if let Some(Group::Paren(has_inner, _)) = self.groups.last() {
*has_inner == false
} else {
false
}
}
fn get_previous_span(&self) -> Option<Span> {
let stack_span = self.stack.back().map(|i| match i {
Either::Left(e) => e.span,
Either::Right(op) => op.span,
});
let op_span = self.operators.back().map(|op| op.span);
match (stack_span, op_span) {
(Some(stack), Some(op)) => {
if stack.is_after(&op) {
stack_span
} else {
op_span
}
}
(Some(_), None) => stack_span,
(None, Some(_)) => op_span,
(None, None) => None,
}
}
fn parse(&mut self, walker: &mut Walker) {
#[derive(PartialEq)]
enum State {
Operand,
AfterOperand,
}
let mut state = State::Operand;
let mut invalidate_rest = false;
'main: while !walker.is_done() {
let (token, span) = match walker.peek() {
Some(t) => t,
None => break 'main,
};
match token {
ConditionToken::Num(num) if state == State::Operand => {
self.stack.push_back(Either::Left(Node {
ty: NodeTy::Num(*num),
span,
}));
state = State::AfterOperand;
self.set_op_rhs_toggle();
walker.push_colour(span, SyntaxType::Number);
}
ConditionToken::Num(_) if state == State::AfterOperand => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundOperandAfterOperand(
self.get_previous_span().unwrap(),
span,
),
));
invalidate_rest = true;
break 'main;
}
ConditionToken::Ident(_) if state == State::Operand => {
self.stack.push_back(Either::Left(Node {
ty: NodeTy::Num(0),
span,
}));
state = State::AfterOperand;
self.set_op_rhs_toggle();
walker.push_colour(span, SyntaxType::Ident);
}
ConditionToken::Ident(_) if state == State::AfterOperand => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundOperandAfterOperand(
self.get_previous_span().unwrap(),
span,
),
));
invalidate_rest = true;
break 'main;
}
ConditionToken::Sub if state == State::Operand => {
self.set_op_rhs_toggle();
self.push_operator(Op {
span,
ty: OpTy::Neg(false),
});
walker.push_colour(span, SyntaxType::Operator);
}
ConditionToken::Not if state == State::Operand => {
self.set_op_rhs_toggle();
self.push_operator(Op {
span,
ty: OpTy::Not(false),
});
walker.push_colour(span, SyntaxType::Operator);
}
ConditionToken::Flip if state == State::Operand => {
self.set_op_rhs_toggle();
self.push_operator(Op {
span,
ty: OpTy::Flip(false),
});
walker.push_colour(span, SyntaxType::Operator);
}
ConditionToken::Not | ConditionToken::Flip
if state == State::AfterOperand =>
{
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::InvalidBinOrPostOperator(span),
));
invalidate_rest = true;
break 'main;
}
ConditionToken::LParen if state == State::Operand => {
self.set_op_rhs_toggle();
self.operators.push_back(Op {
span,
ty: OpTy::ParenStart,
});
self.groups.push(Group::Paren(false, span));
walker.push_colour(span, SyntaxType::Punctuation);
}
ConditionToken::LParen if state == State::AfterOperand => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundOperandAfterOperand(
self.get_previous_span().unwrap(),
span,
),
));
invalidate_rest = true;
break 'main;
}
ConditionToken::RParen if state == State::AfterOperand => {
match self.end_paren(span) {
Ok(_) => {}
Err(_) => {
invalidate_rest = true;
break 'main;
}
}
walker.push_colour(span, SyntaxType::Punctuation);
}
ConditionToken::RParen if state == State::Operand => {
let prev_op_span = self.get_previous_span();
let just_started_paren = self.just_started_paren();
match self.end_paren(span) {
Ok(_) => {}
Err(_) => {
invalidate_rest = true;
break 'main;
}
}
if just_started_paren {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundEmptyParens(Span::new(
prev_op_span.unwrap().start,
span.end,
)),
));
} else {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundRParenInsteadOfOperand(
prev_op_span.unwrap(),
span,
),
))
}
state = State::AfterOperand;
walker.push_colour(span, SyntaxType::Punctuation);
}
ConditionToken::Defined if state == State::Operand => {
walker.disable_macro_expansion = true;
let defined_kw_span = span;
walker.push_colour(defined_kw_span, SyntaxType::Keyword);
walker.advance();
let (token, token_span) = match walker.peek() {
Some(t) => t,
None => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::ExpectedIdentOrLParenAfterDefinedOp(
defined_kw_span.next_single_width(),
),
));
break 'main;
}
};
match token {
ConditionToken::Ident(str) => {
let ident = str.clone();
walker.push_colour(token_span, SyntaxType::Ident);
walker.advance();
self.stack.push_back(Either::Left(Node {
ty: NodeTy::Defined(Ident {
name: ident,
span: token_span,
}),
span: Span::new(
defined_kw_span.start,
token_span.end,
),
}));
walker.disable_macro_expansion = false;
continue 'main;
}
ConditionToken::LParen => {
let l_paren_span = token_span;
walker.push_colour(
l_paren_span,
SyntaxType::Punctuation,
);
walker.advance();
let (token, token_span) = match walker.peek() {
Some(t) => t,
None => {
walker.syntax_diags.push(Syntax::Expr(ExprDiag::ExpectedIdentAfterDefineOpLParen(l_paren_span.next_single_width())));
break 'main;
}
};
let (ident, ident_span) = match token {
ConditionToken::Ident(str) => {
let ident = str.clone();
walker.push_colour(
token_span,
SyntaxType::Ident,
);
(
Ident {
name: ident,
span: token_span,
},
token_span,
)
}
_ => {
walker.syntax_diags.push(Syntax::Expr(ExprDiag::ExpectedIdentAfterDefineOpLParen(l_paren_span.next_single_width())));
break 'main;
}
};
walker.advance();
let (token, token_span) = match walker.peek() {
Some(t) => t,
None => {
walker.syntax_diags.push(Syntax::Expr(ExprDiag::ExpectedRParenAfterDefineOpIdent(ident_span.next_single_width())));
break 'main;
}
};
match token {
ConditionToken::RParen => {}
_ => {
walker.syntax_diags.push(Syntax::Expr(ExprDiag::ExpectedRParenAfterDefineOpIdent(ident_span.next_single_width())));
break 'main;
}
}
walker.advance();
self.stack.push_back(Either::Left(Node {
ty: NodeTy::Defined(ident),
span: Span::new(
defined_kw_span.start,
token_span.end,
),
}));
walker.disable_macro_expansion = false;
continue 'main;
}
_ => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::ExpectedIdentOrLParenAfterDefinedOp(
defined_kw_span.next_single_width(),
),
));
break 'main;
}
}
}
ConditionToken::Defined if state == State::AfterOperand => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::FoundOperandAfterOperand(
self.get_previous_span().unwrap(),
span,
),
));
invalidate_rest = true;
break 'main;
}
ConditionToken::InvalidNum(_) => {
invalidate_rest = true;
break 'main;
}
ConditionToken::Invalid(_) => {
walker
.syntax_diags
.push(Syntax::Expr(ExprDiag::FoundInvalidToken(span)));
invalidate_rest = true;
break 'main;
}
_ if state == State::AfterOperand => {
self.push_operator(Op::from_token(token.clone(), span));
state = State::Operand;
walker.push_colour(span, SyntaxType::Operator);
}
_ if state == State::Operand => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::InvalidPrefixOperator(span),
));
invalidate_rest = true;
break 'main;
}
_ => unreachable!(),
}
walker.advance();
}
if invalidate_rest {
walker.advance();
while let Some((_, span)) = walker.peek() {
walker.push_colour(span, SyntaxType::Invalid);
walker.advance();
}
}
if !self.groups.is_empty() {
let group_end = self.get_previous_span().unwrap().end_zero_width();
while let Some(group) = self.groups.last() {
match group {
Group::Paren(_, l_paren) => {
walker.syntax_diags.push(Syntax::Expr(
ExprDiag::UnclosedParens(*l_paren, group_end),
));
}
}
let _ = self.end_paren(group_end);
}
}
while let Some(op) = self.operators.pop_back() {
self.stack.push_back(Either::Right(op));
}
}
fn create_ast(&mut self) -> Option<Expr> {
if self.stack.is_empty() {
return None;
}
let mut stack: VecDeque<Expr> = VecDeque::new();
let pop_back = |stack: &mut VecDeque<Expr>| stack.pop_back().unwrap();
while let Some(item) = self.stack.pop_front() {
match item {
Either::Left(node) => match node.ty {
NodeTy::Num(num) => stack.push_back(Expr {
span: node.span,
ty: ExprTy::Num(num),
}),
NodeTy::Defined(ident) => stack.push_back(Expr {
span: node.span,
ty: ExprTy::Defined { ident },
}),
},
Either::Right(op) => match op.ty {
OpTy::Neg(has_operand) => {
let expr = if has_operand {
Some(pop_back(&mut stack))
} else {
None
};
let span = if let Some(ref expr) = expr {
Span::new(op.span.start, expr.span.end)
} else {
op.span
};
stack.push_back(Expr {
span,
ty: ExprTy::Prefix {
op: PreOp {
span: op.span,
ty: PreOpTy::Neg,
},
expr: expr.map(|e| Box::from(e)),
},
});
}
OpTy::Flip(has_operand) => {
let expr = if has_operand {
Some(pop_back(&mut stack))
} else {
None
};
let span = if let Some(ref expr) = expr {
Span::new(op.span.start, expr.span.end)
} else {
op.span
};
stack.push_back(Expr {
span,
ty: ExprTy::Prefix {
op: PreOp {
span: op.span,
ty: PreOpTy::Flip,
},
expr: expr.map(|e| Box::from(e)),
},
});
}
OpTy::Not(has_operand) => {
let expr = if has_operand {
Some(pop_back(&mut stack))
} else {
None
};
let span = if let Some(ref expr) = expr {
Span::new(op.span.start, expr.span.end)
} else {
op.span
};
stack.push_back(Expr {
span,
ty: ExprTy::Prefix {
op: PreOp {
span: op.span,
ty: PreOpTy::Not,
},
expr: expr.map(|e| Box::from(e)),
},
});
}
OpTy::Paren(has_inner, _l_paren, _end) => {
let expr = if has_inner {
Some(pop_back(&mut stack))
} else {
None
};
stack.push_back(Expr {
span: op.span,
ty: ExprTy::Parens {
expr: expr.map(|e| Box::from(e)),
},
});
}
OpTy::Add(has_rhs)
| OpTy::Sub(has_rhs)
| OpTy::Mul(has_rhs)
| OpTy::Div(has_rhs)
| OpTy::Rem(has_rhs)
| OpTy::And(has_rhs)
| OpTy::Or(has_rhs)
| OpTy::Xor(has_rhs)
| OpTy::LShift(has_rhs)
| OpTy::RShift(has_rhs)
| OpTy::EqEq(has_rhs)
| OpTy::NotEq(has_rhs)
| OpTy::Gt(has_rhs)
| OpTy::Lt(has_rhs)
| OpTy::Ge(has_rhs)
| OpTy::Le(has_rhs)
| OpTy::AndAnd(has_rhs)
| OpTy::OrOr(has_rhs) => {
let last = pop_back(&mut stack);
let (left, right) = if has_rhs {
(pop_back(&mut stack), Some(last))
} else {
(last, None)
};
let span = if let Some(ref right) = right {
Span::new(left.span.start, right.span.end)
} else {
Span::new(left.span.start, op.span.end)
};
let bin_op = op.to_bin_op();
stack.push_back(Expr {
ty: ExprTy::Binary {
left: Box::from(left),
op: bin_op,
right: right.map(|e| Box::from(e)),
},
span,
});
}
_ => {
unreachable!("Invalid operator {op:?} in conditional expression shunting yard stack. This operator should never be present in the final RPN stack.");
}
},
}
}
if stack.len() != 1 {
unreachable!("After processing the conditional expression shunting yard output stack, we are left with more than one expression. This should not happen.");
}
Some(stack.pop_back().unwrap())
}
}
#[cfg(test)]
mod tests {
use crate::{
diag::{ExprDiag, Syntax},
lexer::{NumType, OpTy, Token},
parser::{
ast::{conditional::*, Ident},
Macro,
},
span,
};
use std::collections::HashMap;
macro_rules! assert_expr {
($macros:expr, $src:expr, $result:expr) => {
let mut lexer = crate::lexer::Lexer::<crate::lexer::Utf16>::new(
$src,
crate::SpanEncoding::Utf16,
);
let tokens =
crate::lexer::preprocessor::parse_condition_tokens(&mut lexer);
assert_eq!(
super::cond_parser(
tokens,
&$macros,
crate::SpanEncoding::Utf16
)
.0
.unwrap(),
$result
);
};
($src:expr, $result:expr) => {
let mut lexer = crate::lexer::Lexer::<crate::lexer::Utf16>::new(
$src,
crate::SpanEncoding::Utf16,
);
let tokens =
crate::lexer::preprocessor::parse_condition_tokens(&mut lexer);
let macros = std::collections::HashMap::new();
assert_eq!(
super::cond_parser(tokens, ¯os, crate::SpanEncoding::Utf16)
.0
.unwrap(),
$result
);
};
}
macro_rules! assert_expr_err {
($macros:expr, $src:expr, $result:expr, $($error:expr),+) => {
let mut lexer = crate::lexer::Lexer::<crate::lexer::Utf16>::new($src, crate::SpanEncoding::Utf16);
let tokens =
crate::lexer::preprocessor::parse_condition_tokens(&mut lexer);
let (expr, syntax, _) = super::cond_parser(tokens, &$macros, crate::SpanEncoding::Utf16);
assert_eq!(expr.unwrap(), $result);
assert_eq!(
syntax,
vec![
$($error),*
]
);
};
($src:expr, $result:expr, $($error:expr),+) => {
let mut lexer = crate::lexer::Lexer::<crate::lexer::Utf16>::new($src, crate::SpanEncoding::Utf16);
let tokens =
crate::lexer::preprocessor::parse_condition_tokens(&mut lexer);
let macros = std::collections::HashMap::new();
let (expr, syntax, _) = super::cond_parser(tokens, ¯os, crate::SpanEncoding::Utf16);
assert_eq!(expr.unwrap(), $result);
assert_eq!(
syntax,
vec![
$($error),*
]
);
};
}
macro_rules! assert_err {
($src:expr, $($error:expr),+) => {
let mut lexer = crate::lexer::Lexer::<crate::lexer::Utf16>::new($src, crate::SpanEncoding::Utf16);
let tokens =
crate::lexer::preprocessor::parse_condition_tokens(&mut lexer);
let macros = std::collections::HashMap::new();
let (expr, syntax, _) = super::cond_parser(tokens, ¯os, crate::SpanEncoding::Utf16);
assert_eq!(expr, None);
assert_eq!(
syntax,
vec![
$($error),*
]
);
};
}
#[test]
fn single_value() {
assert_expr!(
"0",
Expr {
ty: ExprTy::Num(0),
span: span(0, 1),
}
);
assert_expr!(
"1",
Expr {
ty: ExprTy::Num(1),
span: span(0, 1),
}
);
assert_expr!(
"50",
Expr {
ty: ExprTy::Num(50),
span: span(0, 2),
}
);
assert_expr!(
"undefined",
Expr {
ty: ExprTy::Num(0),
span: span(0, 9),
}
);
assert_expr!(
"foo_bar_11245_",
Expr {
ty: ExprTy::Num(0),
span: span(0, 14),
}
);
assert_expr_err!(
"50 0",
Expr {
ty: ExprTy::Num(50),
span: span(0, 2),
},
Syntax::Expr(ExprDiag::FoundOperandAfterOperand(
span(0, 2),
span(3, 4),
))
);
assert_expr_err!(
"50 foo",
Expr {
ty: ExprTy::Num(50),
span: span(0, 2),
},
Syntax::Expr(ExprDiag::FoundOperandAfterOperand(
span(0, 2),
span(3, 6),
))
);
}
#[test]
fn pre() {
assert_expr!(
"-100",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(100),
span: span(1, 4),
})),
},
span: span(0, 4),
}
);
assert_expr!(
"~1",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Flip,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(1),
span: span(1, 2),
})),
},
span: span(0, 2),
}
);
assert_expr!(
"!90",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Not,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(90),
span: span(1, 3),
})),
},
span: span(0, 3),
}
);
assert_expr!(
"-undefined",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(0),
span: span(1, 10),
})),
},
span: span(0, 10),
}
);
assert_expr!(
"--3",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(1, 2),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(3),
span: span(2, 3),
})),
},
span: span(1, 3),
})),
},
span: span(0, 3),
}
);
assert_expr!(
"!-3",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Not,
span: span(0, 1)
},
expr: Some(Box::from(Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(1, 2),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(3),
span: span(2, 3),
})),
},
span: span(1, 3),
})),
},
span: span(0, 3),
}
);
assert_expr_err!(
"5!",
Expr {
ty: ExprTy::Num(5),
span: span(0, 1),
},
Syntax::Expr(ExprDiag::InvalidBinOrPostOperator(span(1, 2)))
);
assert_expr_err!(
"5~",
Expr {
ty: ExprTy::Num(5),
span: span(0, 1),
},
Syntax::Expr(ExprDiag::InvalidBinOrPostOperator(span(1, 2)))
);
assert_expr_err!(
"-- *",
Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(0, 1),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Neg,
span: span(1, 2),
},
expr: None
},
span: span(1, 2),
}))
},
span: span(0, 2),
},
Syntax::Expr(ExprDiag::InvalidPrefixOperator(span(3, 4)))
);
}
#[test]
fn defined() {
assert_expr!(
"defined bar",
Expr {
ty: ExprTy::Defined {
ident: Ident {
name: "bar".into(),
span: span(8, 11),
},
},
span: span(0, 11),
}
);
assert_expr!(
"defined(foo)",
Expr {
ty: ExprTy::Defined {
ident: Ident {
name: "foo".into(),
span: span(8, 11),
},
},
span: span(0, 12),
}
);
assert_expr!(
"defined ( bar_2 )",
Expr {
ty: ExprTy::Defined {
ident: Ident {
name: "bar_2".into(),
span: span(10, 15),
},
},
span: span(0, 17),
}
);
assert_err!(
"defined",
Syntax::Expr(ExprDiag::ExpectedIdentOrLParenAfterDefinedOp(span(
7, 8
)))
);
assert_err!(
"defined +",
Syntax::Expr(ExprDiag::ExpectedIdentOrLParenAfterDefinedOp(span(
7, 8
)))
);
assert_err!(
"defined (",
Syntax::Expr(ExprDiag::ExpectedIdentAfterDefineOpLParen(span(
9, 10
)))
);
assert_err!(
"defined ( foo",
Syntax::Expr(ExprDiag::ExpectedRParenAfterDefineOpIdent(span(
13, 14
)))
);
assert_err!(
"defined ( +",
Syntax::Expr(ExprDiag::ExpectedIdentAfterDefineOpLParen(span(
9, 10
)))
);
}
#[test]
fn binary() {
assert_expr!(
"5 + 6",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(5),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Add,
span: span(2, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(6),
span: span(4, 5),
})),
},
span: span(0, 5),
}
);
assert_expr!(
"5 - 8",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(5),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(2, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(8),
span: span(4, 5),
})),
},
span: span(0, 5),
}
);
assert_expr!(
"5 * 1 - 25",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(5),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Mul,
span: span(2, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(1),
span: span(4, 5),
})),
},
span: span(0, 5),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(6, 7),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(25),
span: span(8, 10),
})),
},
span: span(0, 10),
}
);
assert_expr!(
"16 / 7 - 4 + 3",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(16),
span: span(0, 2),
}),
op: BinOp {
ty: BinOpTy::Div,
span: span(3, 4),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(7),
span: span(5, 6),
})),
},
span: span(0, 6),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(7, 8),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(4),
span: span(9, 10),
})),
},
span: span(0, 10),
}),
op: BinOp {
ty: BinOpTy::Add,
span: span(11, 12),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(3),
span: span(13, 14),
})),
},
span: span(0, 14),
}
);
assert_expr!(
"0 - !1",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(0),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(2, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Prefix {
op: PreOp {
ty: PreOpTy::Not,
span: span(4, 5),
},
expr: Some(Box::from(Expr {
ty: ExprTy::Num(1),
span: span(5, 6),
}))
},
span: span(4, 6),
}))
},
span: span(0, 6),
}
);
assert_expr_err!(
"0 - 5 ! 4",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(0),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(2, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(5),
span: span(4, 5),
}))
},
span: span(0, 5),
},
Syntax::Expr(ExprDiag::InvalidBinOrPostOperator(span(6, 7)))
);
assert_expr_err!(
"0 - * 5",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(0),
span: span(0, 1),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(2, 3),
},
right: None,
},
span: span(0, 3),
},
Syntax::Expr(ExprDiag::InvalidPrefixOperator(span(4, 5)))
);
}
#[test]
fn undefined_object_macro() {
assert_expr!(
"FOO",
Expr {
ty: ExprTy::Num(0),
span: span(0, 3),
}
);
assert_expr!(
"FOO_BAR_22",
Expr {
ty: ExprTy::Num(0),
span: span(0, 10),
}
);
assert_expr_err!(
"FOO BAR",
Expr {
ty: ExprTy::Num(0),
span: span(0, 3),
},
Syntax::Expr(ExprDiag::FoundOperandAfterOperand(
span(0, 3),
span(4, 7)
))
);
}
#[test]
fn object_macro_expansion() {
let mut macros = HashMap::new();
macros.insert(
"FOO".to_owned(),
(
span(0, 0),
Macro::Object(vec![(
Token::Num {
type_: NumType::Dec,
num: "2".into(),
suffix: None,
},
span(0, 1),
)]),
),
);
assert_expr!(
macros,
"FOO",
Expr {
ty: ExprTy::Num(2),
span: span(0, 3),
}
);
let mut macros = HashMap::new();
macros.insert(
"FOO".to_owned(),
(
span(0, 0),
Macro::Object(vec![
(
Token::Num {
type_: NumType::Dec,
num: "50".into(),
suffix: None,
},
span(0, 2),
),
(Token::Op(OpTy::Sub), span(3, 4)),
(
Token::Num {
type_: NumType::Dec,
num: "5".into(),
suffix: None,
},
span(5, 6),
),
]),
),
);
assert_expr!(
macros,
"FOO",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(50),
span: span(0, 3),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(0, 3),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(5),
span: span(0, 3),
})),
},
span: span(0, 3),
}
);
}
#[test]
fn undefined_function_macro() {
assert_expr_err!(
"FOO()",
Expr {
ty: ExprTy::Num(0),
span: span(0, 3)
},
Syntax::Expr(ExprDiag::FoundOperandAfterOperand(
span(0, 3),
span(3, 4)
))
);
}
#[test]
fn function_macro_expansion() {
let mut macros = HashMap::new();
macros.insert(
"FOO".to_owned(),
(
span(0, 0),
Macro::Function {
params: vec![],
body: vec![(
Token::Num {
type_: NumType::Dec,
num: "2".into(),
suffix: None,
},
span(0, 1),
)],
},
),
);
assert_expr!(
macros,
"FOO()",
Expr {
ty: ExprTy::Num(2),
span: span(0, 5),
}
);
let mut macros = HashMap::new();
macros.insert(
"FOO".to_owned(),
(
span(0, 0),
Macro::Function {
params: vec![Ident {
name: "A".into(),
span: span(0, 1),
}],
body: vec![
(Token::Ident("A".into()), span(0, 1)),
(Token::Op(OpTy::Sub), span(2, 3)),
(
Token::Num {
type_: NumType::Dec,
num: "2".into(),
suffix: None,
},
span(4, 5),
),
],
},
),
);
assert_expr!(
macros,
"FOO(3)",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(3),
span: span(0, 6),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(0, 6),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(2),
span: span(0, 6),
})),
},
span: span(0, 6),
}
);
let mut macros = HashMap::new();
macros.insert(
"FOO".to_owned(),
(
span(0, 0),
Macro::Function {
params: vec![Ident {
name: "A".into(),
span: span(0, 1),
}],
body: vec![
(Token::Ident("A".into()), span(0, 1)),
(Token::Op(OpTy::Sub), span(2, 3)),
(
Token::Num {
type_: NumType::Dec,
num: "2".into(),
suffix: None,
},
span(4, 5),
),
],
},
),
);
macros.insert(
"BAR".to_owned(),
(
span(0, 0),
Macro::Function {
params: vec![Ident {
name: "b".into(),
span: span(0, 1),
}],
body: vec![(Token::Ident("B".into()), span(0, 1))],
},
),
);
assert_expr_err!(
macros,
"FOO(3) BAR(2)",
Expr {
ty: ExprTy::Binary {
left: Box::from(Expr {
ty: ExprTy::Num(3),
span: span(0, 6),
}),
op: BinOp {
ty: BinOpTy::Sub,
span: span(0, 6),
},
right: Some(Box::from(Expr {
ty: ExprTy::Num(2),
span: span(0, 6),
})),
},
span: span(0, 6),
},
Syntax::Expr(ExprDiag::FoundOperandAfterOperand(
span(0, 6),
span(7, 13)
))
);
}
}