#[macro_use]
extern crate log;
extern crate xswag_base as base;
extern crate lalrpop_util;
pub mod lex;
pub mod ast;
pub mod grammar;
use base::diag::{Remark, Report, Snippet};
use base::code::{BytePos, FileMap, Span};
use lalrpop_util::ParseError;
use lex::{Token, TokenSpan};
use std::cell::RefCell;
use ast::ItemExt;
const MAX_PREVIEW_LEN: usize = 15;
pub fn parse_compilation_unit(file: &FileMap)
-> (Option<ast::CompilationUnit>, Vec<Report>)
{
let tokens = RefCell::new(Vec::new());
let lexing_err = RefCell::new(None);
let lexer = lex::Tokenizer::new(file)
.take_while(|res| match res {
&Ok(_) => true,
&Err(ref e) => {
*lexing_err.borrow_mut() = Some(e.clone());
false
}
})
.map(Result::unwrap)
.inspect(|t| tokens.borrow_mut().push(t.clone()))
.map(|ts| { (ts.span.lo, ts.tok, ts.span.hi) })
.filter(|t| t.1.is_real());
let mut errors = Vec::new();
let res = grammar::java8::parse_CompilationUnit(&mut errors, lexer);
let res = res.map_err(|e| match e {
ParseError::User { error: e } => e,
ParseError::InvalidToken { location: loc } => {
Report::simple_error("lalrpop InvalidToken", Span::single(loc))
},
ParseError::UnrecognizedToken { token, expected } => {
match token {
None => {
Report::simple_spanless_error(
"Unexpected end of file (EOF) while parsing. Maybe you \
forgot to close a parentheses or brace?",
)
},
Some((lo, tok, hi)) => {
handle_unexpected_token(
tok,
Span::new(lo, hi),
file,
&expected,
&tokens.borrow(),
)
}
}
},
ParseError::ExtraToken { token: (lo, tok, hi) } => {
Report::simple_error(
format!("lalrpop ExtraToken {:?}", tok),
Span::new(lo, hi),
)
},
});
let mut reps = Vec::new();
let ast = if let Some(ref lexing_err) = *lexing_err.borrow() {
reps.push(lexing_err.report.clone());
None
} else {
res.map(|ast| Some(ast)).unwrap_or_else(|e| { reps.push(e); None })
};
if let Some(ref ast) = ast {
let fname = file.filename()
.trim_right_matches(".java")
.trim_right_matches(".jav");
let wrong_types = ast.types
.iter()
.filter(|ty| ty.vis() == ast::Visibility::Public)
.filter_map(|ty| ty.ident())
.filter(|&ident| ident.name != fname);
for &ast::Ident { ref name, span } in wrong_types {
let msg = format!(
"a public type called '{}' should be declared in a file \
called '{}.java'. Either rename your file appropriately \
or rename the type to '{}'",
name,
name,
fname
);
reps.push(Report::simple_error(msg, span));
}
}
reps.extend_from_slice(&errors);
(ast, reps)
}
fn handle_unexpected_token(
token: Token,
span: Span,
file: &FileMap,
expected: &[String],
tokens: &[TokenSpan]
) -> Report {
let tok_name = token.as_java_string();
let mut chs = file.src()[span.into_range()].chars();
let mut tok_str: String = chs.by_ref().take(MAX_PREVIEW_LEN).collect();
if chs.next().is_some() {
tok_str.push_str("...");
}
let mut msg = format!("unexpected '{}'", tok_name);
if tok_name != tok_str {
msg.push_str(&format!(" (`{}`)", tok_str));
}
if expected.len() == 1 {
msg.push_str(&format!(". Expected `{}`", expected[0]));
} else if expected.len() > 1 {
msg.push_str(&format!(". Expected one of {:?}", expected));
}
let rep = Report::simple_error(msg, span);
let loc_lo = file.get_loc(span.lo);
let line = file.get_line(loc_lo.line).unwrap();
let is_line_start = line
.char_indices()
.take_while(|&(i, _)| i < loc_lo.col.0 as usize)
.all(|(_, c)| c.is_whitespace());
let prev = tokens
.iter()
.rev()
.skip_while(|ts| ts.span.lo >= span.lo)
.find(|ts| ts.tok.is_real());
let is_good = prev.map(|ts| ts.tok == Token::Semi || ts.tok == Token::BraceOp);
if is_line_start && is_good == Some(false) {
rep.with_remark(Remark::note(
"maybe you forgot a semicolon (`;`) at the end of this line?",
Snippet::Replace {
span: Span::empty_at(prev.unwrap().span.hi),
with: ";".into(),
},
))
} else {
rep.with_note("this is a syntax error. Make sure all \
parentheses are balanced and nothing is missing.")
}
}