///|
priv struct Lexer {
input : String
source : String
mut offset : Int
mut line : Int
mut column : Int
}
///|
fn Lexer::span(self : Lexer, start : Int, line : Int, column : Int) -> Span {
{ source: self.source, start, end: self.offset, line, column, }
}
///|
fn Lexer::current(self : Lexer) -> Char? {
self.input.get_char(self.offset)
}
///|
fn Lexer::peek(self : Lexer, distance : Int) -> Char? {
self.input.get_char(self.offset + distance)
}
///|
fn Lexer::advance(self : Lexer) -> Char? {
match self.current() {
None => None
Some(char) => {
self.offset += char.utf16_len()
if char == '\n' {
self.line += 1
self.column = 1
} else {
self.column += 1
}
Some(char)
}
}
}
///|
fn is_identifier_start(char : Char) -> Bool {
char.is_ascii_alphabetic() || char == '_'
}
///|
fn is_identifier_continue(char : Char) -> Bool {
is_identifier_start(char) || char.is_ascii_digit() || char == '.'
}
///|
fn Lexer::skip_line_comment(self : Lexer) -> Unit {
while self.current() is Some(char) && char != '\n' {
ignore(self.advance())
}
}
///|
fn Lexer::skip_block_comment(self : Lexer, start : Span) -> Unit raise IdlError {
ignore(self.advance())
ignore(self.advance())
while self.current() is Some(char) {
if char == '*' && self.peek(1) == Some('/') {
ignore(self.advance())
ignore(self.advance())
return
}
ignore(self.advance())
}
raise UnterminatedComment(start)
}
///|
fn Lexer::skip_trivia(self : Lexer) -> Unit raise IdlError {
while self.current() is Some(char) {
if char.is_ascii_whitespace() {
ignore(self.advance())
} else if char == '#' {
self.skip_line_comment()
} else if char == '/' && self.peek(1) == Some('/') {
self.skip_line_comment()
} else if char == '/' && self.peek(1) == Some('*') {
let start = {
source: self.source,
start: self.offset,
end: self.offset,
line: self.line,
column: self.column,
}
self.skip_block_comment(start)
} else {
return
}
}
}
///|
fn Lexer::identifier(self : Lexer) -> Token {
let start = self.offset
let line = self.line
let column = self.column
while self.current() is Some(char) && is_identifier_continue(char) {
ignore(self.advance())
}
let value = self.input.unsafe_substring(start~, end=self.offset)
{ kind: Identifier(value), span: self.span(start, line, column), }
}
///|
fn Lexer::number(self : Lexer) -> Token raise IdlError {
let start = self.offset
let line = self.line
let column = self.column
if self.current() == Some('-') {
ignore(self.advance())
}
let mut floating = false
if self.current() == Some('0') &&
(self.peek(1) == Some('x') || self.peek(1) == Some('X')) {
ignore(self.advance())
ignore(self.advance())
while self.current() is Some(char) && char.is_ascii_hexdigit() {
ignore(self.advance())
}
} else {
while self.current() is Some(char) && char.is_ascii_digit() {
ignore(self.advance())
}
if self.current() == Some('.') &&
self.peek(1) is Some(next) &&
next.is_ascii_digit() {
floating = true
ignore(self.advance())
while self.current() is Some(char) && char.is_ascii_digit() {
ignore(self.advance())
}
}
if self.current() is Some('e' | 'E') {
floating = true
ignore(self.advance())
if self.current() is Some('+' | '-') {
ignore(self.advance())
}
while self.current() is Some(char) && char.is_ascii_digit() {
ignore(self.advance())
}
}
}
let text = self.input.unsafe_substring(start~, end=self.offset)
if floating {
let value : Double = @str.from_str(text) catch {
_ => raise InvalidInteger(text, self.span(start, line, column))
}
{ kind: FloatLiteral(value), span: self.span(start, line, column), }
} else {
let value : Int64 = @str.from_str(text) catch {
_ => raise InvalidInteger(text, self.span(start, line, column))
}
{ kind: IntegerLiteral(value), span: self.span(start, line, column), }
}
}
///|
fn Lexer::string_literal(self : Lexer) -> Token raise IdlError {
let start = self.offset
let line = self.line
let column = self.column
let quote = self.advance().unwrap()
let value = StringBuilder()
while self.current() is Some(char) {
if char == quote {
ignore(self.advance())
return {
kind: StringLiteral(value.to_string()),
span: self.span(start, line, column),
}
}
if char == '\n' || char == '\r' {
raise UnterminatedString(self.span(start, line, column))
}
ignore(self.advance())
if char == '\\' {
let escape_span = self.span(self.offset - 1, self.line, self.column - 1)
match self.advance() {
Some('n') => value.write_char('\n')
Some('r') => value.write_char('\r')
Some('t') => value.write_char('\t')
Some('\\') => value.write_char('\\')
Some('\'') => value.write_char('\'')
Some('"') => value.write_char('"')
Some(other) => raise InvalidEscape(other, escape_span)
None => raise UnterminatedString(self.span(start, line, column))
}
} else {
value.write_char(char)
}
}
raise UnterminatedString(self.span(start, line, column))
}
///|
fn is_symbol(char : Char) -> Bool {
match char {
'{'
| '}'
| '['
| ']'
| '('
| ')'
| '<'
| '>'
| ':'
| ';'
| ','
| '='
| '*'
| '+' => true
_ => false
}
}
///|
fn Lexer::next_token(self : Lexer) -> Token raise IdlError {
self.skip_trivia()
let start = self.offset
let line = self.line
let column = self.column
match self.current() {
None =>
{
kind: End,
span: { source: self.source, start, end: start, line, column, },
}
Some(char) if is_identifier_start(char) => self.identifier()
Some('-') if self.peek(1) is Some(next) && next.is_ascii_digit() =>
self.number()
Some(char) if char.is_ascii_digit() => self.number()
Some('\'' | '"') => self.string_literal()
Some(char) if is_symbol(char) => {
ignore(self.advance())
{ kind: Symbol(char), span: self.span(start, line, column), }
}
Some(char) => {
ignore(self.advance())
raise UnexpectedCharacter(char, self.span(start, line, column))
}
}
}
///|
/// Tokenizes one Thrift IDL document without accessing the file system.
pub fn lex(
input : String,
source? : String = "",
) -> Array[Token] raise IdlError {
let lexer = { input, source, offset: 0, line: 1, column: 1, }
let tokens : Array[Token] = []
while true {
let token = lexer.next_token()
let done = token.kind is End
tokens.push(token)
if done {
break
}
}
tokens
}