use base::code::{FileMap, Span, SrcOffset, BytePos};
use base::diag;
use std::iter::Iterator;
use std::str::Chars;
use std::str::FromStr;
use super::token::*;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Error<P> {
pub report: diag::Report,
pub poison: Option<P>,
}
impl<P> Error<P> {
pub fn map_poison<F, U>(self, op: F) -> Error<U>
where F: FnOnce(P) -> U
{
Error {
report: self.report,
poison: self.poison.map(op),
}
}
pub fn map_report<F>(self, op: F) -> Error<P>
where F: FnOnce(diag::Report) -> diag::Report
{
Error {
report: op(self.report),
poison: self.poison,
}
}
}
pub type Result<T, P> = ::std::result::Result<T, Error<P>>;
fn map_both<T, U, F>(res: Result<T, T>, op: F) -> Result<U, U>
where F: FnOnce(T) -> U
{
match res {
Ok(t) => Ok(op(t)),
Err(Error { report: rep, poison: p }) =>
Err(Error { report: rep, poison: p.map(op) }),
}
}
enum ScannedChar {
Eof, Normal(char), Escape(char), InvalidEscape(char), }
pub struct Tokenizer<'a> {
fmap: &'a FileMap,
chs: Chars<'a>,
last: Option<char>,
curr: Option<char>,
peek: Option<char>,
last_pos: BytePos,
curr_pos: BytePos,
peek_pos: BytePos,
token_start: BytePos,
upeek: Option<char>,
escaped_peek: u8,
bump_err: Option<diag::Report>,
}
impl<'a> Tokenizer<'a> {
pub fn new(fmap: &'a FileMap) -> Tokenizer<'a> {
let mut tok = Tokenizer {
chs: fmap.src().chars(),
fmap: fmap,
last: None,
curr: None,
peek: None,
last_pos: BytePos(0),
curr_pos: BytePos(0),
peek_pos: BytePos(0),
token_start: BytePos(0),
upeek: None,
escaped_peek: 0,
bump_err: None
};
tok.dbump();
tok
}
pub fn next_token(&mut self) -> Option<Result<TokenSpan, TokenSpan>> {
let res = self.next_token_inner();
let out = match self.bump_err.take() {
None => res,
Some(rep) => Some(Err(Error {
report: rep,
poison: res.and_then(|r| r.map(|o| Some(o))
.unwrap_or_else(|e| e.poison))
}))
};
match out {
Some(ref r) => trace!("Produced Token: {:?}", r),
None => trace!("Produced None token!"),
}
out
}
fn next_token_inner(&mut self) -> Option<Result<TokenSpan, TokenSpan>> {
self.token_start = self.curr_pos;
let p = self.peek.unwrap_or('\0');
let curr = match self.curr {
None => return None,
Some(c) => c,
};
let res: Result<Token, Token> = match curr {
c if is_java_whitespace(c) => {
self.skip_whitespace();
Ok(Token::Whitespace)
},
'/' if p == '/' || p == '*' => {
match self.skip_comment() {
Some(r) => Err(Error {
report: r,
poison: Some(Token::Comment),
}),
None => Ok(Token::Comment),
}
},
'(' => { self.bump(); Ok(Token::ParenOp) },
')' => { self.bump(); Ok(Token::ParenCl) },
'{' => { self.bump(); Ok(Token::BraceOp) },
'}' => { self.bump(); Ok(Token::BraceCl) },
'[' => { self.bump(); Ok(Token::BracketOp) },
']' => { self.bump(); Ok(Token::BracketCl) },
';' => { self.bump(); Ok(Token::Semi) },
',' => { self.bump(); Ok(Token::Comma) },
'.' => {
match p {
'0' ... '9' => {
self.scan_number_literal()
.map(|l| Token::Literal(l))
.map_err(|e| Error {
report: e.report,
poison: None,
})
},
_ => {
self.bump();
if p == '.' && self.peek == Some('.') {
self.dbump();
Ok(Token::DotDotDot)
} else {
Ok(Token::Dot)
}
}
}
},
'@' => { self.bump(); Ok(Token::At) },
':' if p == ':' => { self.dbump(); Ok(Token::ColonSep) },
':' => { self.bump(); Ok(Token::Colon) },
'=' if p == '=' => { self.dbump(); Ok(Token::EqEq) },
'=' => { self.bump(); Ok(Token::Eq) },
'>' if p == '>' => {
self.dbump();
match self.curr.unwrap_or('\0') {
'>' => {
self.bump();
if self.curr == Some('=') {
self.bump();
Ok(Token::ShrUnEq)
} else {
Ok(Token::ShrUn)
}
},
'=' => {
self.bump();
Ok(Token::ShrEq)
}
_ => {
Ok(Token::Shr)
}
}
},
'>' if p == '=' => { self.dbump(); Ok(Token::Ge) },
'>' => { self.bump(); Ok(Token::Gt) },
'<' if p == '<' => {
self.dbump();
if self.curr == Some('=') {
self.bump();
Ok(Token::ShlEq)
} else {
Ok(Token::Shl)
}
},
'<' if p == '=' => { self.dbump(); Ok(Token::Le) },
'<' => { self.bump(); Ok(Token::Lt) },
'!' if p == '=' => { self.dbump(); Ok(Token::Ne) },
'!' => { self.bump(); Ok(Token::Bang) },
'~' => { self.bump(); Ok(Token::Tilde) },
'?' => { self.bump(); Ok(Token::Question) },
'+' if p == '=' => { self.dbump(); Ok(Token::PlusEq) },
'+' if p == '+' => { self.dbump(); Ok(Token::PlusPlus) },
'+' => { self.bump(); Ok(Token::Plus) },
'-' if p == '=' => { self.dbump(); Ok(Token::MinusEq) },
'-' if p == '>' => { self.dbump(); Ok(Token::Arrow) },
'-' if p == '-' => { self.dbump(); Ok(Token::MinusMinus) },
'-' => { self.bump(); Ok(Token::Minus) },
'&' if p == '=' => { self.dbump(); Ok(Token::AndEq) },
'&' if p == '&' => { self.dbump(); Ok(Token::AndAnd) },
'&' => { self.bump(); Ok(Token::And) },
'|' if p == '=' => { self.dbump(); Ok(Token::OrEq) },
'|' if p == '|' => { self.dbump(); Ok(Token::OrOr) },
'|' => { self.bump(); Ok(Token::Or) },
'*' if p == '=' => { self.dbump(); Ok(Token::StarEq) },
'*' => { self.bump(); Ok(Token::Star) },
'/' if p == '=' => { self.dbump(); Ok(Token::SlashEq) },
'/' => { self.bump(); Ok(Token::Slash) },
'^' if p == '=' => { self.dbump(); Ok(Token::CaretEq) },
'^' => { self.bump(); Ok(Token::Caret) },
'%' if p == '=' => { self.dbump(); Ok(Token::PercentEq) },
'%' => { self.bump(); Ok(Token::Percent) },
'"' => {
map_both(self.scan_string_literal(),
|s| Token::Literal(Lit::Str(s)))
},
'\'' => {
map_both(self.scan_char_literal(),
|c| Token::Literal(Lit::Char(c)))
},
'0' ... '9' => self.scan_number_literal()
.map(|l| Token::Literal(l))
.map_err(|e| Error {
report: e.report,
poison: None,
}),
c if is_java_ident_start(c) => Ok(self.scan_word()),
_ => {
return Some(Err(Error {
report: diag::Report::simple_error(
"illegal character in this context",
self.curr_span()
),
poison: None,
}))
},
};
let add_span = |tok| TokenSpan {
tok: tok,
span: self.curr_span(),
};
Some(res.map(|t| add_span(t))
.map_err(|e| e.map_poison(|tok| add_span(tok))))
}
fn bump(&mut self) {
self.last = self.curr;
self.curr = self.peek;
self.peek = if let Some(un) = self.upeek {
self.upeek = None;
Some(un)
} else {
self.chs.next()
};
self.last_pos = self.curr_pos;
self.curr_pos = self.peek_pos;
if let Some(c) = self.curr {
self.peek_pos = self.peek_pos + if self.escaped_peek > 0 {
BytePos(self.escaped_peek as SrcOffset)
} else {
BytePos(c.len_utf8() as SrcOffset)
};
self.escaped_peek = 0;
}
if self.curr == Some('\n')
|| (self.curr == Some('\r') && self.peek != Some('\n'))
{
self.fmap.add_line(self.peek_pos);
}
if self.peek == Some('\\') && self.curr != Some('\\') {
self.upeek = self.chs.next();
if self.upeek == Some('u') {
self.upeek = None;
let mut pos_offset = 2;
let mut peekiter = self.chs.by_ref().peekable();
while let Some(&'u') = peekiter.peek() {
pos_offset += 1;
peekiter.next();
}
let mut value = 0;
let mut num_digits = 0;
let mut interrupt = None;
for c in peekiter.by_ref().take(4) {
match c.to_digit(16) {
Some(v) => {
value += v << ((3-num_digits)*4);
num_digits += 1;
},
None => {
interrupt = Some(c);
break;
},
}
}
if num_digits < 4 {
self.bump_err = Some(diag::Report::simple_error(
"Invalid unicode escape (less than 4 digits)",
Span {
lo: self.peek_pos,
hi: self.peek_pos +
BytePos(pos_offset + num_digits),
}
));
self.peek = interrupt;
self.peek_pos.0 += pos_offset + num_digits;
} else {
self.peek = match ::std::char::from_u32(value) {
Some(c) => {
self.escaped_peek = 4 + pos_offset as u8;
Some(c)
},
None => {
self.bump_err = Some(diag::Report::simple_error(
"Invalid unicode escape (not a valid unicode \
scalar value)",
Span {
lo: self.peek_pos,
hi: self.peek_pos + BytePos(pos_offset + 4),
}
));
self.peek_pos.0 += pos_offset + 4;
peekiter.next()
}
};
}
}
}
if self.peek == Some('\u{001a}') {
self.upeek = self.chs.next();
if self.upeek == None {
self.peek = None
}
}
}
fn dbump(&mut self) {
self.bump();
self.bump();
}
fn curr_span(&self) -> Span {
Span::new(self.token_start, self.curr_pos)
}
fn simple_error<P, S: Into<String>>(&self, poison: P, msg: S) -> Error<P> {
Error {
report: diag::Report::simple_error(msg, self.curr_span()),
poison: Some(poison),
}
}
fn skip_whitespace(&mut self) {
while is_java_whitespace(self.curr.unwrap_or('x')) {
self.bump();
}
}
fn skip_comment(&mut self) -> Option<diag::Report> {
if self.peek == Some('*') {
self.dbump();
loop {
match (self.curr, self.peek) {
(Some('*'), Some('/')) => break,
(_, None) => {
self.dbump();
return Some(diag::Report::simple_error(
"unclosed comment block",
self.curr_span()
));
}
_ => self.bump(),
}
}
self.dbump(); } else {
loop {
match self.curr {
None | Some('\n') | Some('\r') => break,
_ => self.bump(),
}
}
}
None
}
fn scan_word(&mut self) -> Token {
let mut s = String::new();
loop {
match self.curr {
Some(c) if is_java_ident_part(c) => {
s.push(c);
self.bump();
},
_ => break,
}
}
match &s[..] {
"true" => Token::Literal(Lit::Bool(true)),
"false" => Token::Literal(Lit::Bool(false)),
"null" => Token::Literal(Lit::Null),
_ => match Keyword::from_str(&s) {
Ok(k) => Token::KeyW(k),
Err(_) => Token::Ident(s),
},
}
}
fn scan_escaped_char(&mut self) -> ScannedChar {
match self.curr {
Some('\\') => {
self.bump();
let out = match self.curr {
None => return ScannedChar::Eof,
Some(c) => {
self.bump();
match c {
'b' => '\u{0008}',
't' => '\t',
'n' => '\n',
'f' => '\u{000c}',
'r' => '\r',
'\'' => '\'',
'\"' => '\"',
'\\' => '\\',
'0' ... '7' => self.scan_octal_escape(),
_ => return ScannedChar::InvalidEscape(c),
}
},
};
ScannedChar::Escape(out)
},
Some(c) => {
self.bump();
ScannedChar::Normal(c)
},
None => ScannedChar::Eof,
}
}
fn scan_octal_escape(&mut self) -> char {
let mut val = self.last.unwrap().to_digit(8).unwrap();
if let Some(c) = self.curr.and_then(|c| c.to_digit(8)) {
self.bump();
val = val * 8 + c;
}
if val < 0o40 {
if let Some(c) = self.curr.and_then(|c| c.to_digit(8)) {
self.bump();
val = val * 8 + c;
}
}
::std::char::from_u32(val).unwrap()
}
fn scan_string_literal(&mut self) -> Result<String, String> {
self.bump();
let mut s = String::new();
loop {
match self.scan_escaped_char() {
ScannedChar::Normal('\"') => break,
ScannedChar::Normal(c) | ScannedChar::Escape(c) => s.push(c),
ScannedChar::InvalidEscape(c) => {
s.push(c);
let e = self.simple_error(s,
format!("invalid escape character `\\{}`", c)
).map_report(|r| r.with_note("valid escape characters are \
\\b \\t \\n \\f \\r \\\" \\' \\\\ or octal escapes"
));
return Err(e);
},
ScannedChar::Eof => return Err(self.simple_error(s,
"unexpected EOF in string literal")),
}
}
Ok(s)
}
fn scan_char_literal(&mut self) -> Result<char, char> {
self.bump(); match self.scan_escaped_char() {
ScannedChar::Normal('\'') => {
Err(self.simple_error('\0', "empty character literal")
.map_report(|r| r.with_note("maybe you want to use an \
empty string `\"\"` instead?")))
},
ScannedChar::Normal(c) | ScannedChar::Escape(c) => {
if self.curr == Some('\'') {
self.bump();
Ok(c)
} else {
Err(self.simple_error('\0', "unclosed character literal"))
}
},
ScannedChar::InvalidEscape(c) => {
Err(self.simple_error(c,
format!("invalid escape character `\\{}`", c)
).map_report(|r| r.with_note("valid escape characters are \
\\b \\t \\n \\f \\r \\\" \\' \\\\ or octal escapes"
))
)
},
ScannedChar::Eof => {
Err(self.simple_error('\0',
"unexpected EOF in character literal"))
}
}
}
fn scan_number_literal(&mut self) -> Result<Lit, ()> {
let (r, s) = match self.curr {
Some('0') => {
match self.peek.unwrap_or('\0') {
'x' | 'X' => {
self.dbump(); (16, if self.curr != Some('.') {
try!(self.scan_digits(16))
} else {
"".into()
})
},
'b' | 'B' => {
self.dbump(); (2, try!(self.scan_digits(2)))
},
'0' ... '9' => {
self.bump(); (8, try!(self.scan_digits(8)))
},
_ => {
self.bump();
(10, "0".into())
}
}
},
Some('.') => (10, "".into()),
_ => (10, try!(self.scan_digits(10)))
};
match self.curr.unwrap_or('\0') {
'l' | 'L' => {
self.bump();
Ok(Lit::Integer { raw: s, is_long: true, radix: r as u8 })
},
c @ 'f' | c @ 'F' | c @ 'd' | c @ 'D' if !s.is_empty() => {
self.bump();
Ok(Lit::Float {
raw: s,
is_double: (c != 'f' && c != 'F'),
radix: r as u8,
exp: "".into(),
})
},
'p' | 'P' | 'e' | 'E' if !s.is_empty() => {
match self.scan_float_exp(r == 16) {
None => {
Err(if r == 16 {
self.simple_error((), "invalid exponent indicator \
for hex float literal (use 'p' or 'P' instead")
} else {
self.simple_error((), "invalid exponent indicator \
for decimal float literal (use 'e' or 'E' \
instead)")
})
},
Some(ex) => {
let double = self.scan_double_suffix().unwrap_or(true);
Ok(Lit::Float {
raw: s,
is_double: double,
radix: r as u8,
exp: ex,
})
}
}
}
'.' => {
if r != 10 && r != 16 {
return Err(self.simple_error((), format!("float literals \
may only be expressed in decimal or hexadecimal, not \
base {}", r)));
}
self.bump();
let fraction = try!(self.scan_digits(r));
if r == 16 && s.is_empty() && fraction.is_empty() {
return Err(self.simple_error((), "hex float literals need \
either a whole number or a fraction part"));
}
let exp = self.scan_float_exp(r == 16).unwrap_or("".into());
if r == 16 && exp.is_empty() {
return Err(self.simple_error((), "hex float literals are \
required to have an exponent"));
}
let is_double = self.scan_double_suffix().unwrap_or(true);
Ok(Lit::Float {
raw: format!("{}.{}", s, fraction),
is_double: is_double,
radix: r as u8,
exp: exp,
})
},
_ => Ok(Lit::Integer { raw: s, is_long: false, radix: r as u8 }),
}
}
fn scan_double_suffix(&mut self) -> Option<bool> {
match self.curr.unwrap_or('\0') {
'd' | 'D' => { self.bump(); Some(true) },
'f' | 'F' => { self.bump(); Some(false) },
_ => None
}
}
fn scan_float_exp(&mut self, hex: bool) -> Option<String> {
match (hex, self.curr.unwrap_or('\0')) {
(false, 'e') | (false, 'E') | (true, 'p') | (true, 'P') => {
self.bump();
Some(if self.curr == Some('-') {
self.bump();
format!("-{}", self.scan_digits(10).unwrap())
} else {
self.scan_digits(10).unwrap()
})
},
_ => None,
}
}
fn scan_digits(&mut self, radix: u32) -> Result<String, ()> {
let scan_radix = if radix <= 10 { 10 } else { radix };
let mut rep = diag::Report {
kind: diag::ReportKind::Error,
span: None,
remarks: vec![],
};
let mut s = String::new();
loop {
match self.curr.unwrap_or('\0') {
'_' => {
self.bump();
continue;
},
c if c.to_digit(scan_radix).is_some() => {
if c.to_digit(radix).is_none() {
rep.remarks.push(diag::Remark::error(
format!("Invalid digit for base{} literal", radix),
diag::Snippet::Orig(Span::single(self.curr_pos)),
));
}
s.push(c);
self.bump();
}
_ => break,
}
}
if rep.remarks.len() > 0 {
let lo = rep.remarks.iter().map(|rem|
rem.snippet.span().unwrap().lo
).min();
let hi = rep.remarks.iter().map(|rem|
rem.snippet.span().unwrap().hi
).min();
rep.span = Some(Span::new(lo.unwrap(), hi.unwrap()));
Err(Error {
report: rep,
poison: None,
})
} else {
Ok(s)
}
}
}
impl<'a> Iterator for Tokenizer<'a> {
type Item = Result<TokenSpan, TokenSpan>;
fn next(&mut self) -> Option<Result<TokenSpan, TokenSpan>> {
self.next_token()
}
}
fn is_java_ident_start(c: char) -> bool {
match c {
'$' | '_' | '¢' | '£' | '¤' | '¥' => true,
'\u{345}' | '\u{37f}' => false,
_ => c.is_alphabetic(),
}
}
fn is_java_ident_part(c: char) -> bool {
match c {
'\u{ad}'
| '\u{000}' ... '\u{008}'
| '\u{00e}' ... '\u{01b}'
| '\u{07f}' ... '\u{09f}'
| '\u{300}' ... '\u{374}' => true,
'\u{37f}' => false,
_ => c.is_numeric() || is_java_ident_start(c),
}
}
fn is_java_whitespace(c: char) -> bool {
match c {
' ' | '\t' | '\u{000c}' | '\n' | '\r' => true,
_ => false,
}
}