use std::{
collections::{hash_map::Entry, VecDeque},
convert::TryInto,
iter::FusedIterator,
};
use arrayvec::ArrayVec;
use derive_more::From;
use rowan::{NodeOrToken, SyntaxElementChildren, TextSize};
use lang_util::{
located::{HasFileNumber, Resolver},
position::NodeSpan,
FileId,
};
use crate::{
parser::{self, Ast, PreprocessorLang, SyntaxKind::*, SyntaxNode, SyntaxToken},
types::path::ParsedPath,
util::{LineMap, Unescaped},
};
use super::{
definition::{Definition, MacroInvocation},
event::{ErrorKind, Event, ProcessingErrorKind},
nodes::{
Define, Directive, DirectiveResult, Elif, Else, Empty, EndIf, Error as ErrorDirective,
Extension, If, IfDef, IfNDef, Include, Invalid, Line, ParsedLine, Pragma, Undef, Version,
},
IncludeMode, ProcessorState,
};
mod if_stack;
use if_stack::IfStack;
pub struct ExpandLocation {
current_file: FileId,
line_map: LineMap,
line_override: Option<(u32, ParsedLine)>,
}
impl Resolver for ExpandLocation {
fn resolve(&self, offset: TextSize) -> (u32, u32) {
self.offset_to_line_and_col(offset)
}
}
impl HasFileNumber for ExpandLocation {
fn current_file(&self) -> FileId {
self.current_file
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocationString<'p> {
Number(u32),
String(&'p str),
}
impl LocationString<'_> {
pub fn is_number(&self) -> bool {
matches!(self, Self::Number(_))
}
}
impl std::fmt::Display for LocationString<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LocationString::Number(num) => write!(f, "{}", num),
LocationString::String(path) => write!(f, "{}", path),
}
}
}
impl ExpandLocation {
pub fn new(current_file: FileId) -> Self {
Self {
current_file,
line_map: Default::default(),
line_override: Default::default(),
}
}
pub fn current_file(&self) -> FileId {
self.current_file
}
pub fn line_override(&self) -> Option<&(u32, ParsedLine)> {
self.line_override.as_ref()
}
pub fn offset_to_raw_line_and_col(&self, offset: TextSize) -> (u32, u32) {
self.line_map.get_line_and_col(offset.into())
}
pub fn offset_to_line_and_col(&self, offset: TextSize) -> (u32, u32) {
let line_and_col = self.offset_to_raw_line_and_col(offset);
(self.line_to_line_number(line_and_col.0), line_and_col.1)
}
pub fn line_to_line_number(&self, raw_line: u32) -> u32 {
if let Some((origin, line_override)) = &self.line_override {
let offset = line_override.line_number() as i64 - *origin as i64 - 2;
(raw_line as i64 + offset) as u32
} else {
raw_line
}
}
pub fn add_override(&mut self, current_offset: TextSize, line: ParsedLine) {
let raw_line = self.line_map.get_line_and_col(current_offset.into()).0;
self.line_override = match self.line_override.take() {
Some((_, prev_override)) => match line {
ParsedLine::Line(line_number) => Some(match prev_override {
ParsedLine::Line(_) => line,
ParsedLine::LineAndFileNumber(_, file_number) => {
ParsedLine::LineAndFileNumber(line_number, file_number)
}
ParsedLine::LineAndPath(_, path) => ParsedLine::LineAndPath(line_number, path),
}),
ParsedLine::LineAndFileNumber(_, _) | ParsedLine::LineAndPath(_, _) => Some(line),
},
None => Some(line),
}
.map(|ov| (raw_line, ov));
}
pub fn string(&self) -> LocationString {
if let Some(line_override) = &self.line_override {
match &line_override.1 {
ParsedLine::LineAndFileNumber(_, number) => {
return LocationString::Number(*number);
}
ParsedLine::LineAndPath(_, path) => {
return LocationString::String(path.as_str());
}
_ => {}
}
}
LocationString::Number(self.current_file.number())
}
}
pub(crate) struct ExpandOne {
if_stack: IfStack,
location: ExpandLocation,
state: ExpandState,
}
#[allow(clippy::large_enum_variant)]
enum ExpandState {
Init {
ast: Ast,
current_state: ProcessorState,
},
Iterate {
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
current_state: ProcessorState,
},
EnterNewFile {
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
current_state: ProcessorState,
path: ParsedPath,
node: SyntaxNode,
},
PendingOne {
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
node_or_token: NodeOrToken<SyntaxNode, SyntaxToken>,
current_state: ProcessorState,
},
PendingEvents {
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
events: ArrayVec<Event, 3>,
current_state: ProcessorState,
},
ExpandedTokens {
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
events: VecDeque<Event>,
current_state: ProcessorState,
},
Complete,
}
#[derive(From)]
enum HandleNodeResult {
Event(Event),
EnterFile(Event, SyntaxNode, ParsedPath),
}
impl ExpandOne {
pub fn new(parsed_file: impl Into<(FileId, Ast)>, current_state: ProcessorState) -> Self {
let (file_id, ast) = parsed_file.into();
Self {
if_stack: IfStack::new(),
location: ExpandLocation::new(file_id),
state: ExpandState::Init { ast, current_state },
}
}
pub fn state(&self) -> Option<&ProcessorState> {
match &self.state {
ExpandState::Init { current_state, .. }
| ExpandState::Iterate { current_state, .. }
| ExpandState::EnterNewFile { current_state, .. }
| ExpandState::PendingOne { current_state, .. }
| ExpandState::PendingEvents { current_state, .. }
| ExpandState::ExpandedTokens { current_state, .. } => Some(current_state),
ExpandState::Complete => None,
}
}
pub fn set_state(&mut self, new_state: ProcessorState) {
match &mut self.state {
ExpandState::Init { current_state, .. }
| ExpandState::Iterate { current_state, .. }
| ExpandState::EnterNewFile { current_state, .. }
| ExpandState::PendingOne { current_state, .. }
| ExpandState::PendingEvents { current_state, .. }
| ExpandState::ExpandedTokens { current_state, .. } => {
*current_state = new_state;
}
ExpandState::Complete => {
panic!("cannot update the state on a completed expand");
}
}
}
pub fn location(&self) -> &ExpandLocation {
&self.location
}
fn handle_node(
&mut self,
current_state: &mut ProcessorState,
node: SyntaxNode,
) -> HandleNodeResult {
match node.kind() {
PP_EMPTY => Event::directive(
Directive::new(self.location.current_file(), node, Empty),
!self.if_stack.active(),
),
PP_VERSION => {
let active = self.if_stack.active();
let directive: DirectiveResult<Version> =
(self.location.current_file(), node).try_into();
match directive {
Ok(directive) => {
if active {
current_state.version = *directive;
}
Event::directive(directive, !active)
}
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_EXTENSION => {
let active = self.if_stack.active();
let directive: DirectiveResult<Extension> =
(self.location.current_file(), node).try_into();
match directive {
Ok(directive) => {
if active {
current_state.extension(&*directive);
}
Event::directive(directive, !active)
}
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_DEFINE => {
let active = self.if_stack.active();
let directive: DirectiveResult<Define> =
(self.location.current_file(), node).try_into();
match directive {
Ok(define) => {
let error = if active {
if define.name().starts_with("GL_") {
Some(ProcessingErrorKind::ProtectedDefine {
ident: define.name().into(),
is_undef: false,
})
} else {
let definition = Definition::Regular(
(*define).clone().into(),
self.location.current_file(),
);
match current_state.definitions.entry(define.name().into()) {
Entry::Occupied(mut entry) => {
if entry.get().protected() {
Some(ProcessingErrorKind::ProtectedDefine {
ident: define.name().into(),
is_undef: false,
})
} else {
*entry.get_mut() = definition;
None
}
}
Entry::Vacant(entry) => {
entry.insert(definition);
None
}
}
}
} else {
None
};
Event::directive_errors(define, !active, error, &self.location)
}
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_IFDEF => {
let active = self.if_stack.active();
let directive: DirectiveResult<IfDef> =
(self.location.current_file(), node).try_into();
let (result, ret) = match directive {
Ok(ifdef) => {
let is_defined = current_state.definitions.contains_key(&ifdef.ident);
(is_defined, Event::directive(ifdef, !active))
}
Err(error) => (true, Event::directive_error(error, &self.location, !active)),
};
self.if_stack.on_if_like(result);
ret
}
PP_IFNDEF => {
let active = self.if_stack.active();
let directive: DirectiveResult<IfNDef> =
(self.location.current_file(), node).try_into();
let (result, ret) = match directive {
Ok(ifndef) => {
let is_defined = current_state.definitions.contains_key(&ifndef.ident);
(!is_defined, Event::directive(ifndef, !active))
}
Err((error, node)) => (
true,
Event::directive_error(
(ProcessingErrorKind::DirectiveIfNDef(error), node),
&self.location,
!active,
),
),
};
self.if_stack.on_if_like(result);
ret
}
PP_IF => {
let active = self.if_stack.active();
let directive: DirectiveResult<If> =
(self.location.current_file(), node).try_into();
let (result, ret) = match directive {
Ok(if_) => {
let (value, error) = if active {
if_.eval(current_state, &self.location)
} else {
(true, None)
};
(
value,
Event::directive_errors(
if_,
!active,
error.map(ProcessingErrorKind::DirectiveIf),
&self.location,
),
)
}
Err(error) => (true, Event::directive_error(error, &self.location, !active)),
};
self.if_stack.on_if_like(result);
ret
}
PP_ELIF => {
let active = self.if_stack.if_group_active();
let directive: DirectiveResult<Elif> =
(self.location.current_file(), node).try_into();
let mut errors: ArrayVec<_, 2> = ArrayVec::new();
let expr = match &directive {
Ok(elif_) => {
if self.if_stack.if_group_active() {
let (value, error) = if active {
elif_.eval(current_state, &self.location)
} else {
(true, None)
};
if let Some(error) = error {
errors.push(ProcessingErrorKind::DirectiveElif(error));
}
value
} else {
true
}
}
Err(_) => true,
};
if let Err(error) = self.if_stack.on_elif(expr) {
errors.push(ProcessingErrorKind::from(error));
}
match directive {
Ok(elif_) => Event::directive_errors(elif_, !active, errors, &self.location),
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_ELSE => {
let active = self.if_stack.if_group_active();
let directive: DirectiveResult<Else> =
(self.location.current_file(), node).try_into();
let error = self.if_stack.on_else().err().map(ProcessingErrorKind::from);
match directive {
Ok(else_) => Event::directive_errors(else_, !active, error, &self.location),
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_ENDIF => {
let active = self.if_stack.if_group_active();
let directive: DirectiveResult<EndIf> =
(self.location.current_file(), node).try_into();
let error = self
.if_stack
.on_endif()
.err()
.map(ProcessingErrorKind::from);
match directive {
Ok(endif) => Event::directive_errors(endif, !active, error, &self.location),
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_UNDEF => {
let active = self.if_stack.active();
let directive: DirectiveResult<Undef> =
(self.location.current_file(), node).try_into();
match directive {
Ok(undef) => {
let protected_ident = if active {
if undef.ident.starts_with("GL_") {
Some(undef.ident.clone())
} else if let Some(def) = current_state.definitions.get(&undef.ident) {
if def.protected() {
Some(undef.ident.clone())
} else {
current_state.definitions.remove(&undef.ident);
None
}
} else {
None
}
} else {
None
};
Event::directive_errors(
undef,
!active,
protected_ident.map(|ident| ProcessingErrorKind::ProtectedDefine {
ident,
is_undef: true,
}),
&self.location,
)
}
Err((error, node)) => Event::directive_error(
(ProcessingErrorKind::DirectiveUndef(error), node),
&self.location,
!active,
),
}
}
PP_ERROR => {
let active = self.if_stack.active();
let directive: DirectiveResult<ErrorDirective> =
(self.location.current_file(), node).try_into();
match directive {
Ok(error) => {
let user_error = ProcessingErrorKind::ErrorDirective {
message: error.message.clone(),
};
Event::directive_errors(
error,
!active,
std::iter::once(user_error),
&self.location,
)
}
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_INCLUDE => {
let active = self.if_stack.active();
let directive: DirectiveResult<Include> =
(self.location.current_file(), node.clone()).try_into();
let (directive, path) = match directive {
Ok(directive) => match directive.path(current_state, &self.location) {
Ok(path) => (Ok(directive), Some(path)),
Err(err) => (Err((err, directive.node().clone())), None),
},
err => (err, None),
};
match directive {
Ok(include) => {
let error = match (path, current_state.include_mode) {
(_, IncludeMode::None) if active => {
Some(ErrorKind::Processing(
ProcessingErrorKind::IncludeNotSupported,
))
}
(Some(path), IncludeMode::GoogleInclude { warn }) if active => {
let node = include.node().clone();
return HandleNodeResult::EnterFile(
Event::directive_errors(
include,
!active,
if warn {
Some(ErrorKind::warn_ext_use(
ext_name!("GL_GOOGLE_include_directive"),
None,
NodeSpan::new(
self.location.current_file(),
node.text_range(),
),
&self.location,
))
} else {
None
},
&self.location,
),
node,
path,
);
}
(_, other) => {
if other.warn() {
if matches!(other, IncludeMode::GoogleInclude { .. }) {
Some(ErrorKind::warn_ext_use(
ext_name!("GL_GOOGLE_include_directive"),
None,
NodeSpan::new(
self.location.current_file(),
node.text_range(),
),
&self.location,
))
} else if matches!(other, IncludeMode::ArbInclude { .. }) {
Some(ErrorKind::warn_ext_use(
ext_name!("GL_ARB_shading_language_include"),
None,
NodeSpan::new(
self.location.current_file(),
node.text_range(),
),
&self.location,
))
} else {
None
}
} else {
None
}
}
};
Event::directive_errors(include, !active, error, &self.location)
}
Err(error) => {
if current_state.include_mode == IncludeMode::None {
Event::directive_error(
(ProcessingErrorKind::IncludeNotSupported, node),
&self.location,
!active,
)
} else {
Event::directive_error(error, &self.location, !active)
}
}
}
}
PP_LINE => {
let active = self.if_stack.active();
let directive: DirectiveResult<Line> =
(self.location.current_file(), node).try_into();
let (directive, line) = match directive {
Ok(directive) => match directive.parse(current_state, &self.location) {
Ok(path) => (Ok(directive), Some(path)),
Err(err) => (Err((err, directive.node().clone())), None),
},
err => (err, None),
};
match directive {
Ok(ld) => {
let error = if active {
if let Some(line) = line {
let (line, error) = match line {
ParsedLine::Line(_) | ParsedLine::LineAndFileNumber(_, _) => {
(line, None)
}
ParsedLine::LineAndPath(_, _)
if current_state.cpp_style_line() =>
{
(line, None)
}
ParsedLine::LineAndPath(line, _) => (
ParsedLine::Line(line),
Some(ProcessingErrorKind::CppStyleLineNotSupported),
),
};
self.location
.add_override(ld.node().text_range().start(), line);
error
} else {
None
}
} else {
None
};
Event::directive_errors(ld, !active, error, &self.location)
}
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
PP_PRAGMA => {
let active = self.if_stack.active();
let directive: DirectiveResult<Pragma> =
(self.location.current_file(), node).try_into();
match directive {
Ok(pragma) => Event::directive(pragma, !active),
Err(error) => Event::directive_error(error, &self.location, !active),
}
}
ERROR => {
Event::directive(
Directive::new(self.location.current_file(), node, Invalid),
!self.if_stack.active(),
)
}
_ => {
panic!("unhandled node type: {:?}", node.kind());
}
}
.into()
}
fn handle_token(
&mut self,
current_state: ProcessorState,
token: SyntaxToken,
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
) -> Option<Event> {
if let Some(definition) = (if self.if_stack.active() && token.kind() == IDENT_KW {
Some(Unescaped::new(token.text()).to_string())
} else {
None
})
.and_then(|ident| current_state.definitions.get(ident.as_ref()))
{
match MacroInvocation::parse_raw(
definition,
token.clone(),
iterator.clone(),
&self.location,
) {
Ok(Some((invocation, new_iterator))) => {
self.state = ExpandState::ExpandedTokens {
iterator: new_iterator,
errors,
events: invocation.substitute(¤t_state, &self.location).into(),
current_state,
};
}
Ok(None) => {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
return Some(Event::token((token, self.location.current_file()), false));
}
Err(err) => {
let mut events = ArrayVec::new();
let pos = err.pos();
events.push(Event::error(err.into_inner(), pos, &self.location, false));
events.push(Event::token((token, self.location.current_file()), false));
self.state = ExpandState::PendingEvents {
iterator,
errors,
events,
current_state,
};
}
}
None
} else {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
Some(Event::token(
(token, self.location.current_file()),
!self.if_stack.active(),
))
}
}
fn handle_node_or_token(
&mut self,
mut current_state: ProcessorState,
iterator: SyntaxElementChildren<PreprocessorLang>,
errors: Vec<parser::Error>,
node_or_token: NodeOrToken<SyntaxNode, SyntaxToken>,
) -> Option<Event> {
match node_or_token {
rowan::NodeOrToken::Node(node) => match self.handle_node(&mut current_state, node) {
HandleNodeResult::Event(event) => {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
Some(event)
}
HandleNodeResult::EnterFile(event, node, path) => {
self.state = ExpandState::EnterNewFile {
iterator,
errors,
current_state,
path,
node,
};
Some(event)
}
},
rowan::NodeOrToken::Token(token) => {
self.handle_token(current_state, token, iterator, errors)
}
}
}
}
pub(crate) enum ExpandEvent {
Event(Event),
EnterFile(SyntaxNode, ParsedPath),
Completed(ProcessorState),
}
impl From<Event> for ExpandEvent {
fn from(event: Event) -> Self {
Self::Event(event)
}
}
impl Iterator for ExpandOne {
type Item = ExpandEvent;
fn next(&mut self) -> Option<Self::Item> {
loop {
match std::mem::replace(&mut self.state, ExpandState::Complete) {
ExpandState::Init { ast, current_state } => {
let (root, errors, line_map) = ast.into_inner();
self.location.line_map = line_map;
self.state = ExpandState::Iterate {
iterator: root.children_with_tokens(),
errors,
current_state,
};
return Some(Event::enter_file(self.location.current_file).into());
}
ExpandState::Iterate {
mut iterator,
mut errors,
current_state,
} => {
if let Some(node_or_token) = iterator.next() {
if let Some(first) = errors.first() {
if node_or_token.text_range().end() >= first.pos().start() {
let error = errors.pop().unwrap();
let pos = node_or_token.text_range();
self.state = ExpandState::PendingOne {
iterator,
errors,
node_or_token,
current_state,
};
return Some(
Event::error(
error.into_inner(),
pos,
&self.location,
!self.if_stack.active(),
)
.into(),
);
}
}
if let Some(result) = self.handle_node_or_token(
current_state,
iterator,
errors,
node_or_token,
) {
return Some(result.into());
}
} else {
return Some(ExpandEvent::Completed(current_state));
}
}
ExpandState::EnterNewFile {
iterator,
errors,
current_state,
path,
node,
} => {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
return Some(ExpandEvent::EnterFile(node, path));
}
ExpandState::PendingOne {
iterator,
errors,
node_or_token,
current_state,
} => {
if let Some(result) =
self.handle_node_or_token(current_state, iterator, errors, node_or_token)
{
return Some(result.into());
}
}
ExpandState::PendingEvents {
iterator,
errors,
mut events,
current_state,
} => {
if let Some(event) = events.swap_pop(0) {
self.state = ExpandState::PendingEvents {
iterator,
errors,
events,
current_state,
};
return Some(event.into());
} else {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
}
}
ExpandState::ExpandedTokens {
iterator,
errors,
mut events,
current_state,
} => {
if let Some(event) = events.pop_front() {
self.state = ExpandState::ExpandedTokens {
iterator,
errors,
events,
current_state,
};
return Some(event.into());
} else {
self.state = ExpandState::Iterate {
iterator,
errors,
current_state,
};
}
}
ExpandState::Complete => {
return None;
}
}
}
}
}
impl FusedIterator for ExpandOne {}