use crate::lower::Lowering;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use zerodds_ast::{AnyId, BinaryOp, ConstExpr, ConstExprNode, Literal, UnaryOp};
use zerodds_ast_core::{Id, PendingRef, Ref, ScopedName};
use zerodds_idl_cst::{CstKind, SyntaxNode, TokenKind, TokenValue, literal_value};
use zerodds_idl_parse::DiagnosticCode;
impl Lowering<'_> {
pub(crate) fn expr(&mut self, node: &Arc<SyntaxNode>) -> Option<Id<ConstExprNode>> {
let expr = match node.kind() {
CstKind::LiteralExpr => ConstExpr::Literal(self.literal(node)?),
CstKind::NameExpr => self.name_expr(node)?,
CstKind::ParenExpr => {
return self.first_expr_child(node);
}
CstKind::UnaryExpr => {
let op = unary_op(node)?;
let operand = self.first_expr_child(node)?;
ConstExpr::Unary { op, operand }
}
CstKind::BinaryExpr => {
let op = binary_op(node)?;
let mut operands = self.expr_children(node);
if operands.len() != 2 {
return None;
}
let rhs = operands.pop()?;
let lhs = operands.pop()?;
ConstExpr::Binary { op, lhs, rhs }
}
_ => return self.first_expr_child(node),
};
let span = node.span();
let id = self.ast.add_expr(expr).ok()?;
self.record_expr_origin(id, span);
Some(id)
}
fn name_expr(&mut self, node: &Arc<SyntaxNode>) -> Option<ConstExpr> {
let name = self.scoped_name_of(node)?;
if let Some(enumerator) = self.lookup_enumerator(name.segments()) {
return Some(ConstExpr::Literal(Literal::Enumerator(Ref::Resolved(
enumerator,
))));
}
if let Some(constant) = self.lookup_constant(name.segments()) {
return Some(ConstExpr::NameRef(Ref::Resolved(constant)));
}
Some(ConstExpr::NameRef(Ref::Pending(PendingRef::ScopedName(
name,
))))
}
fn lookup_enumerator(
&self,
segments: &[alloc::string::String],
) -> Option<Id<zerodds_ast::EnumeratorNode>> {
match self.lookup_name(segments)? {
AnyId::Enumerator(id, _) => Some(id),
_ => None,
}
}
fn lookup_constant(
&self,
segments: &[alloc::string::String],
) -> Option<Id<zerodds_ast::ConstNode>> {
match self.lookup_name(segments)? {
AnyId::Const(id, _) => Some(id),
_ => None,
}
}
fn lookup_name(&self, segments: &[alloc::string::String]) -> Option<AnyId> {
match segments {
[] => None,
[single] => self.scopes.lookup(single),
qualified => self
.ast
.find_by_scoped_name(&ScopedName::absolute(qualified.to_vec()))
.ok(),
}
}
pub(crate) fn first_expr_child(&mut self, node: &Arc<SyntaxNode>) -> Option<Id<ConstExprNode>> {
let child = node
.children()
.into_iter()
.find(|child| is_expr(child.kind()))?;
self.expr(&child)
}
fn expr_children(&mut self, node: &Arc<SyntaxNode>) -> Vec<Id<ConstExprNode>> {
node.children()
.into_iter()
.filter(|child| is_expr(child.kind()))
.filter_map(|child| self.expr(&child))
.collect()
}
fn literal(&mut self, node: &Arc<SyntaxNode>) -> Option<Literal> {
let (span, token) = node
.syntax_tokens()
.into_iter()
.find(|(_, token)| token.kind().is_literal())?;
let text = token.text();
let literal = match token.kind() {
TokenKind::True => Literal::Boolean(true),
TokenKind::False => Literal::Boolean(false),
TokenKind::IntLiteral => {
let TokenValue::Int { value, .. } = literal_value(TokenKind::IntLiteral, text)
else {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
span,
"this integer literal has no value",
"OMG IDL 4.2 §7.2.6.1",
);
return None;
};
Literal::Integer(value)
}
TokenKind::FloatLiteral => split_float(text),
TokenKind::FixedLiteral => split_fixed(text),
TokenKind::CharLiteral | TokenKind::WCharLiteral => {
let TokenValue::Char { value } = literal_value(token.kind(), text) else {
return None;
};
if token.kind() == TokenKind::WCharLiteral {
Literal::WChar(value)
} else {
let ch = char::from_u32(value)?;
Literal::Char(ch)
}
}
TokenKind::StringLiteral | TokenKind::WStringLiteral => {
let value =
crate::lower::joined_string_literals(node.syntax_tokens(), token.kind())
.into_iter()
.next()?;
if token.kind() == TokenKind::WStringLiteral {
Literal::WString(value.chars().map(u32::from).collect())
} else {
Literal::String(value)
}
}
_ => return None,
};
Some(literal)
}
}
pub(crate) const fn is_expr(kind: CstKind) -> bool {
matches!(
kind,
CstKind::LiteralExpr
| CstKind::NameExpr
| CstKind::UnaryExpr
| CstKind::BinaryExpr
| CstKind::ParenExpr
)
}
fn unary_op(node: &Arc<SyntaxNode>) -> Option<UnaryOp> {
node.syntax_tokens().into_iter().find_map(|(_, token)| {
Some(match token.kind() {
TokenKind::Plus => UnaryOp::Plus,
TokenKind::Minus => UnaryOp::Minus,
TokenKind::Tilde => UnaryOp::Not,
_ => return None,
})
})
}
fn binary_op(node: &Arc<SyntaxNode>) -> Option<BinaryOp> {
node.syntax_tokens().into_iter().find_map(|(_, token)| {
Some(match token.kind() {
TokenKind::Pipe => BinaryOp::Or,
TokenKind::Caret => BinaryOp::Xor,
TokenKind::Ampersand => BinaryOp::And,
TokenKind::LessLess => BinaryOp::Shl,
TokenKind::GreaterGreater => BinaryOp::Shr,
TokenKind::Plus => BinaryOp::Add,
TokenKind::Minus => BinaryOp::Sub,
TokenKind::Star => BinaryOp::Mul,
TokenKind::Slash => BinaryOp::Div,
TokenKind::Percent => BinaryOp::Mod,
_ => return None,
})
})
}
fn split_float(text: &str) -> Literal {
match text.split_once(['e', 'E']) {
Some((mantissa, exponent)) => Literal::Float {
mantissa: mantissa.to_string(),
exponent: Some(exponent.to_string()),
},
None => Literal::Float {
mantissa: text.to_string(),
exponent: None,
},
}
}
fn split_fixed(text: &str) -> Literal {
let body = text.strip_suffix(['d', 'D']).unwrap_or(text);
match body.split_once('.') {
Some((whole, fraction)) => {
let mut digits = whole.to_string();
digits.push_str(fraction);
Literal::Fixed {
digits,
scale: u8::try_from(fraction.len()).unwrap_or(u8::MAX),
}
}
None => Literal::Fixed {
digits: body.to_string(),
scale: 0,
},
}
}