use crate::base::pos::{self, BytePos, Column, Line, Location, Span, Spanned};
use crate::token::{SpannedToken, Token};
quick_error! {
#[derive(Debug, PartialEq)]
pub enum Error {
UnindentedTooFar {
description("line was unindented too far")
}
}
}
#[derive(Copy, Clone, Debug)]
struct Offside {
location: Location,
context: Context,
}
impl Offside {
fn new(location: Location, context: Context) -> Offside {
Offside {
location: location,
context: context,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum Context {
Block { emit_semi: bool },
Brace,
Bracket,
Paren,
Expr,
Let,
Rec,
Type,
If,
MatchClause,
Lambda,
Attribute,
}
#[derive(Debug)]
struct Contexts {
stack: Vec<Offside>,
}
impl Contexts {
fn new() -> Contexts {
Contexts { stack: Vec::new() }
}
fn last(&self) -> Option<&Offside> {
self.stack.last()
}
fn last_mut(&mut self) -> Option<&mut Offside> {
self.stack.last_mut()
}
fn pop(&mut self) -> Option<Offside> {
self.stack.pop()
}
fn is_empty(&self) -> bool {
self.stack.is_empty()
}
fn push(&mut self, offside: Offside) -> Result<(), Spanned<Error, BytePos>> {
self.check_unindentation_limit(offside)?;
self.stack.push(offside);
Ok(())
}
fn check_unindentation_limit(
&mut self,
offside: Offside,
) -> Result<(), Spanned<Error, BytePos>> {
let mut skip_block = false;
for other_offside in self.stack.iter().rev() {
match other_offside.context {
Context::Lambda => {
skip_block = true;
continue;
}
Context::Block { .. } if skip_block => continue,
Context::Brace | Context::Bracket | Context::Paren => return Ok(()),
Context::MatchClause
| Context::Type
| Context::Rec
| Context::Let
| Context::Block { .. }
if offside.location.column < other_offside.location.column =>
{
()
}
_ => continue,
}
debug!("Unindentation error: {:?} < {:?}", offside, other_offside);
return Err(pos::spanned2(
offside.location.absolute,
offside.location.absolute,
Error::UnindentedTooFar,
));
}
Ok(())
}
}
pub struct Layout<'input, Tokens> {
tokens: Tokens,
unprocessed_tokens: Vec<SpannedToken<'input>>,
indent_levels: Contexts,
}
impl<'input, Tokens> Layout<'input, Tokens>
where
Tokens: Iterator<Item = SpannedToken<'input>>,
{
pub fn new(tokens: Tokens) -> Layout<'input, Tokens> {
Layout {
tokens: tokens,
unprocessed_tokens: Vec::new(),
indent_levels: Contexts::new(),
}
}
fn continue_block(&mut self, context: Context, token: &Token) -> bool {
let in_rec = self.indent_levels.stack.len() >= 2
&& self.indent_levels.stack[self.indent_levels.stack.len() - 2].context == Context::Rec;
in_rec
&& (context == Context::Attribute
|| (*token != Token::Rec && self.scan_continue_block(context, token)))
}
fn scan_continue_block(&mut self, context: Context, first_token: &Token) -> bool {
let expected_token = match context {
Context::Let => Token::Let,
Context::Type => Token::Type,
_ => return false,
};
let mut in_attribute = false;
for i in 0.. {
let peek_token = if i == 0 {
Some(first_token)
} else {
self.peek_token(i - 1).map(|t| &t.value)
};
if peek_token == Some(&expected_token) {
return true;
}
match peek_token {
Some(peek_token) => match peek_token {
Token::AttributeOpen => in_attribute = true,
Token::DocComment(..) => (),
Token::RBracket => in_attribute = false,
_ if !in_attribute => return false,
_ => (),
},
None => return false,
}
}
false
}
fn peek_token(&mut self, n: usize) -> Option<&SpannedToken<'input>> {
for _ in self.unprocessed_tokens.len()..=n {
let token = match self.tokens.next() {
Some(token) => token,
None => return None,
};
self.unprocessed_tokens.insert(0, token);
}
self.unprocessed_tokens.first()
}
fn next_token(&mut self) -> SpannedToken<'input> {
self.unprocessed_tokens.pop().unwrap_or_else(|| {
self.tokens.next().unwrap_or_else(|| {
let location = Location {
line: Line::from(0),
column: Column::from(1),
absolute: BytePos::from(1),
};
pos::spanned2(location, location, Token::EOF)
})
})
}
fn layout_token(
&mut self,
token: SpannedToken<'input>,
layout_token: Token<'input>,
) -> SpannedToken<'input> {
let span = token.span;
self.unprocessed_tokens.push(token);
pos::spanned(span, layout_token)
}
fn scan_for_next_block(&mut self, context: Context) -> Result<(), Spanned<Error, BytePos>> {
let next = self.next_token();
let span = next.span;
self.unprocessed_tokens.push(next);
if let Context::Block { .. } = context {
match self
.indent_levels
.stack
.iter()
.find(|last_offside| match last_offside.context {
Context::Block { .. } => true,
_ => false,
})
.map(|last_offside| last_offside.location.column)
{
Some(last_column) if span.start().column <= last_column => {
debug!(
"Inserting empty block due to {:?} <= {:?}",
self.indent_levels.last(),
span.start().column
);
self.unprocessed_tokens
.push(pos::spanned(span, Token::CloseBlock));
self.unprocessed_tokens
.push(pos::spanned(span, Token::OpenBlock));
return self.indent_levels.push(Offside::new(span.start(), context));
}
_ => (),
}
self.unprocessed_tokens
.push(pos::spanned(span, Token::OpenBlock));
}
self.indent_levels.push(Offside::new(span.start(), context))
}
fn layout_next_token(&mut self) -> Result<SpannedToken<'input>, Spanned<Error, BytePos>> {
use std::cmp::Ordering;
let mut token = self.next_token();
if token.value == Token::EOF {
let mut start = token.span.start();
start.column = Column::from(0);
token.span = Span::new(start, token.span.end());
if self.indent_levels.is_empty() {
return Ok(token);
}
}
loop {
let offside = match (&token.value, self.indent_levels.last().cloned()) {
(&Token::ShebangLine(_), _) => return Ok(token),
(_, Some(offside)) => offside,
(_, None) => {
let offside =
Offside::new(token.span.start(), Context::Block { emit_semi: false });
self.indent_levels.push(offside)?;
return Ok(self.layout_token(token, Token::OpenBlock));
}
};
debug!("--------\n{:?}\n{:?}", token, offside);
match (&token.value, offside.context) {
(&Token::Comma, Context::Brace)
| (&Token::Comma, Context::Paren)
| (&Token::Comma, Context::Bracket) => return Ok(token),
(&Token::In, _)
| (&Token::CloseBlock, _)
| (&Token::Else, _)
| (&Token::RBrace, _)
| (&Token::RBracket, _)
| (&Token::RParen, _)
| (&Token::Comma, _) => {
self.indent_levels.pop();
if self
.indent_levels
.stack
.iter()
.all(|offside| !token_closes_context(&token.value, offside.context))
{
return Ok(token);
}
if token_closes_context(&token.value, offside.context) {
match offside.context {
Context::If => (),
Context::Brace
| Context::Bracket
| Context::Paren
| Context::Attribute => return Ok(token),
Context::Block { .. } if token.value == Token::CloseBlock => {
if let Some(offside) = self.indent_levels.last_mut() {
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = false;
}
}
return Ok(token);
}
Context::Rec | Context::Let | Context::Type => {
let location = {
let offside = self
.indent_levels
.last_mut()
.expect("No top level block found");
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = false;
}
offside.location
};
if let Some(Context::Rec) =
self.indent_levels.last().map(|offside| offside.context)
{
self.indent_levels.pop();
}
let offside =
Offside::new(location, Context::Block { emit_semi: false });
self.indent_levels.push(offside)?;
self.unprocessed_tokens
.push(pos::spanned(token.span, Token::OpenBlock));
return Ok(token);
}
Context::Block { .. } => {
return Ok(self.layout_token(token, Token::CloseBlock));
}
_ => continue,
}
} else {
continue;
}
}
(_, _) => (),
}
let ordering = token.span.start().column.cmp(&offside.location.column);
match (offside.context, ordering) {
(Context::Block { .. }, Ordering::Less) => {
self.unprocessed_tokens.push(token.clone());
token.value = Token::CloseBlock;
continue;
}
(Context::Block { emit_semi: true }, Ordering::Equal) => {
if let Some(offside) = self.indent_levels.last_mut() {
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = false;
}
}
return Ok(self.layout_token(token, Token::Semi));
}
(Context::Block { emit_semi: false }, Ordering::Equal) => {
match token.value {
Token::AttributeOpen | Token::DocComment { .. } | Token::OpenBlock => (),
_ => {
if let Some(offside) = self.indent_levels.last_mut() {
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = true;
}
}
}
}
}
(Context::Expr, _) | (Context::Lambda, _) => {
if ordering != Ordering::Greater {
self.indent_levels.pop();
continue;
}
}
(Context::MatchClause, _) => {
if ordering == Ordering::Less
|| (ordering == Ordering::Equal && token.value != Token::Pipe)
{
self.indent_levels.pop();
continue;
}
}
(Context::Let, Ordering::Equal)
| (Context::Let, Ordering::Less)
| (Context::Type, Ordering::Equal)
| (Context::Type, Ordering::Less)
if token.value != Token::RBrace =>
{
if !self.continue_block(offside.context, &token.value) {
if token.value == Token::EOF {
self.indent_levels.pop();
continue;
}
let let_location = self.indent_levels.pop().unwrap().location;
{
let offside = self
.indent_levels
.last_mut()
.expect("No top level block found");
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = false;
}
}
if let Some(Context::Rec) =
self.indent_levels.last().map(|offside| offside.context)
{
self.indent_levels.pop();
}
let span = token.span;
let result = Ok(self.layout_token(token, Token::In));
let offside =
Offside::new(let_location, Context::Block { emit_semi: false });
self.indent_levels.push(offside)?;
self.unprocessed_tokens
.push(pos::spanned(span, Token::OpenBlock));
return result;
}
}
_ => (),
}
let push_context = match token.value {
Token::Rec => Some(Context::Rec),
Token::Type => Some(Context::Type),
Token::Let => Some(Context::Let),
Token::Do | Token::Seq => Some(Context::Let),
Token::If => Some(Context::If),
Token::Match => Some(Context::Expr),
Token::Lambda => Some(Context::Lambda),
Token::LBrace => Some(Context::Brace),
Token::LBracket => Some(Context::Bracket),
Token::LParen => Some(Context::Paren),
Token::AttributeOpen => Some(Context::Attribute),
_ => None,
};
if let Some(context) = push_context {
let pos = if offside.context == Context::Rec
&& offside.location.line == token.span.start().line
{
offside.location
} else {
token.span.start()
};
if offside.context == context
&& (offside.context == Context::Type || offside.context == Context::Let)
&& (self.indent_levels.stack.len() >= 2
&& self.indent_levels.stack[self.indent_levels.stack.len() - 2].context
== Context::Rec)
{
self.indent_levels.pop();
}
let offside = Offside::new(pos, context);
return self.indent_levels.push(offside).map(move |()| token);
}
match (&token.value, offside.context) {
(&Token::In, context) => {
self.indent_levels.pop();
if let Context::Block { .. } = context {
return Ok(self.layout_token(token, Token::CloseBlock));
}
}
(&Token::Equals, Context::Let)
| (&Token::RArrow, Context::Lambda)
| (&Token::RArrow, Context::MatchClause)
| (&Token::Then, _) => {
self.scan_for_next_block(Context::Block { emit_semi: false })?
}
(&Token::With, _) => self.scan_for_next_block(Context::MatchClause)?,
(&Token::Else, _) => {
let next = self.next_token();
let add_block = next.value != Token::If
|| next.span.start().line != token.span.start().line;
self.unprocessed_tokens.push(next);
if add_block {
self.scan_for_next_block(Context::Block { emit_semi: false })?;
}
}
(&Token::Comma, _) => {
if let Some(offside) = self.indent_levels.last_mut() {
if let Context::Block {
ref mut emit_semi, ..
} = offside.context
{
*emit_semi = false;
}
}
}
(_, _) => (),
}
return Ok(token);
}
}
}
fn token_closes_context(token: &Token, context: Context) -> bool {
match (token, context) {
(&Token::Else, Context::If)
| (&Token::RBrace, Context::Brace)
| (&Token::RBracket, Context::Bracket)
| (&Token::RParen, Context::Paren)
| (&Token::CloseBlock, Context::Block { .. })
| (&Token::In, Context::Rec)
| (&Token::In, Context::Let)
| (&Token::In, Context::Type)
| (&Token::RBracket, Context::Attribute)
| (_, Context::Block { .. }) => true,
(_, _) => false,
}
}
impl<'input, Tokens> Iterator for Layout<'input, Tokens>
where
Tokens: Iterator<Item = SpannedToken<'input>>,
{
type Item = Result<SpannedToken<'input>, Spanned<Error, BytePos>>;
fn next(&mut self) -> Option<Self::Item> {
match self.layout_next_token() {
Ok(Spanned {
value: Token::EOF, ..
}) => None,
token => Some(token),
}
}
}