///|
/// Tokens and recoverable diagnostics produced from one source string.
pub(all) struct ScanResult {
tokens : Array[Token]
diagnostics : Array[Diagnostic]
}
///|
pub fn ScanResult::is_success(self : ScanResult) -> Bool {
self.diagnostics.is_empty()
}
///|
priv struct Lexer {
source : String
tokens : Array[Token]
diagnostics : Array[Diagnostic]
mut start : Int
mut current : Int
mut start_line : Int
mut start_column : Int
mut line : Int
mut column : Int
}
///|
fn Lexer::new(source : String) -> Lexer {
{
source,
tokens: [],
diagnostics: [],
start: 0,
current: 0,
start_line: 1,
start_column: 1,
line: 1,
column: 1,
}
}
///|
fn Lexer::at_end(self : Lexer) -> Bool {
self.current >= self.source.length()
}
///|
fn Lexer::position(self : Lexer) -> SourcePosition {
{ offset: self.current, line: self.line, column: self.column, }
}
///|
fn Lexer::span(self : Lexer) -> SourceSpan {
{
start: {
offset: self.start,
line: self.start_line,
column: self.start_column,
},
end: self.position(),
}
}
///|
fn Lexer::point_span(self : Lexer) -> SourceSpan {
let position = self.position()
{ start: position, end: position, }
}
///|
fn Lexer::lexeme(self : Lexer) -> String {
self.source[self.start:self.current].to_owned()
}
///|
fn Lexer::peek(self : Lexer) -> Int {
if self.at_end() {
-1
} else {
self.source.unsafe_get(self.current).to_int()
}
}
///|
fn Lexer::peek_next(self : Lexer) -> Int {
if self.current + 1 >= self.source.length() {
-1
} else {
self.source.unsafe_get(self.current + 1).to_int()
}
}
///|
fn Lexer::advance(self : Lexer) -> Int {
let code = self.source.unsafe_get(self.current).to_int()
self.current = self.current + 1
self.column = self.column + 1
code
}
///|
fn Lexer::consume_newline(self : Lexer) -> Unit {
self.current = self.current + 1
self.line = self.line + 1
self.column = 1
}
///|
fn Lexer::matches(self : Lexer, expected : Int) -> Bool {
if self.peek() != expected {
false
} else {
ignore(self.advance())
true
}
}
///|
fn Lexer::add(self : Lexer, kind : TokenKind) -> Unit {
self.tokens.push(Token::new(kind, self.lexeme(), self.span()))
}
///|
fn Lexer::report(self : Lexer, code : DiagnosticCode, message : String) -> Unit {
self.diagnostics.push(Diagnostic::new(Scan, code, message, self.span()))
}
///|
fn is_digit(code : Int) -> Bool {
code >= 48 && code <= 57
}
///|
fn is_hex_digit(code : Int) -> Bool {
is_digit(code) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102)
}
///|
fn is_alpha(code : Int) -> Bool {
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code == 95 ||
code >= 128
}
///|
fn is_alpha_numeric(code : Int) -> Bool {
is_alpha(code) || is_digit(code)
}
///|
fn Lexer::identifier(self : Lexer) -> Unit {
while is_alpha_numeric(self.peek()) {
ignore(self.advance())
}
let text = self.lexeme()
let kind = if text.length() >= 2 &&
text.unsafe_get(0).to_int() == 95 &&
text.unsafe_get(1).to_int() == 95 {
StaticField
} else if text.length() >= 1 && text.unsafe_get(0).to_int() == 95 {
Field
} else {
keyword_kind(text)
}
self.add(kind)
}
///|
fn Lexer::decimal_tail(self : Lexer) -> Unit {
while is_digit(self.peek()) || self.peek() == 95 {
ignore(self.advance())
}
if self.peek() == 46 && is_digit(self.peek_next()) {
ignore(self.advance())
while is_digit(self.peek()) || self.peek() == 95 {
ignore(self.advance())
}
}
if self.peek() == 69 || self.peek() == 101 {
ignore(self.advance())
if self.peek() == 43 || self.peek() == 45 {
ignore(self.advance())
}
let exponent_start = self.current
while is_digit(self.peek()) || self.peek() == 95 {
ignore(self.advance())
}
if self.current == exponent_start {
self.report(InvalidNumber, "expected exponent digits")
}
}
}
///|
fn Lexer::number(self : Lexer, first : Int) -> Unit {
if first == 48 && (self.peek() == 120 || self.peek() == 88) {
ignore(self.advance())
let digits_start = self.current
while is_hex_digit(self.peek()) || self.peek() == 95 {
ignore(self.advance())
}
if self.current == digits_start {
self.report(InvalidNumber, "expected hexadecimal digits")
}
} else {
self.decimal_tail()
}
if parse_number_value(self.lexeme()).is_inf() {
self.report(InvalidNumber, "number literal is too large")
}
self.add(Number)
}
///|
fn valid_escape(code : Int) -> Bool {
code == 34 ||
code == 37 ||
code == 48 ||
code == 85 ||
code == 92 ||
code == 97 ||
code == 98 ||
code == 101 ||
code == 102 ||
code == 110 ||
code == 114 ||
code == 116 ||
code == 117 ||
code == 120 ||
code == 118
}
///|
fn Lexer::string(self : Lexer) -> Unit {
let mut interpolation_depth = 0
let mut expression_string = false
while !self.at_end() {
let code = self.peek()
if interpolation_depth == 0 && code == 34 {
break
}
if code == 10 {
self.consume_newline()
} else if code == 92 {
ignore(self.advance())
if self.at_end() {
break
}
let escaped = self.advance()
if !valid_escape(escaped) {
self.report(InvalidEscape, "invalid string escape")
} else {
let digits = if escaped == 120 {
2
} else if escaped == 117 {
4
} else if escaped == 85 {
8
} else {
0
}
for digit_index = 0; digit_index < digits; digit_index = digit_index + 1 {
if !is_hex_digit(self.peek()) {
self.report(InvalidEscape, "invalid string escape")
break
}
ignore(self.advance())
}
}
} else if interpolation_depth == 0 && code == 37 && self.peek_next() == 40 {
ignore(self.advance())
ignore(self.advance())
interpolation_depth = 1
} else if interpolation_depth > 0 {
ignore(self.advance())
if expression_string {
if code == 34 {
expression_string = false
}
} else if code == 34 {
expression_string = true
} else if code == 40 {
interpolation_depth = interpolation_depth + 1
} else if code == 41 {
interpolation_depth = interpolation_depth - 1
}
} else {
ignore(self.advance())
}
}
if self.at_end() || interpolation_depth != 0 {
self.diagnostics.push(
Diagnostic::new(
Scan,
UnterminatedString,
"unterminated string",
self.point_span(),
),
)
return
}
ignore(self.advance())
self.add(StringLiteral)
}
///|
fn Lexer::raw_string(self : Lexer) -> Unit {
// The first quote was consumed by scan_one(). Consume the remaining two.
ignore(self.advance())
ignore(self.advance())
while !self.at_end() {
if self.peek() == 34 &&
self.peek_next() == 34 &&
self.current + 2 < self.source.length() &&
self.source.unsafe_get(self.current + 2).to_int() == 34 {
ignore(self.advance())
ignore(self.advance())
ignore(self.advance())
self.add(StringLiteral)
return
}
if self.peek() == 10 {
self.consume_newline()
} else {
ignore(self.advance())
}
}
self.diagnostics.push(
Diagnostic::new(
Scan,
UnterminatedString,
"unterminated raw string",
self.point_span(),
),
)
}
///|
fn Lexer::line_comment(self : Lexer) -> Unit {
while !self.at_end() && self.peek() != 10 {
ignore(self.advance())
}
}
///|
fn Lexer::block_comment(self : Lexer) -> Unit {
let mut depth = 1
while depth > 0 && !self.at_end() {
if self.peek() == 47 && self.peek_next() == 42 {
ignore(self.advance())
ignore(self.advance())
depth = depth + 1
} else if self.peek() == 42 && self.peek_next() == 47 {
ignore(self.advance())
ignore(self.advance())
depth = depth - 1
} else if self.peek() == 10 {
self.consume_newline()
} else {
ignore(self.advance())
}
}
if depth > 0 {
self.diagnostics.push(
Diagnostic::new(
Scan,
UnterminatedComment,
"unterminated block comment",
self.point_span(),
),
)
}
}
///|
fn Lexer::scan_one(self : Lexer) -> Unit {
let code = self.advance()
match code {
40 => self.add(LeftParen)
41 => self.add(RightParen)
123 => self.add(LeftBrace)
125 => self.add(RightBrace)
91 => self.add(LeftBracket)
93 => self.add(RightBracket)
58 => self.add(Colon)
44 => self.add(Comma)
45 => self.add(Minus)
43 => self.add(Plus)
42 => self.add(Star)
37 => self.add(Percent)
94 => self.add(Caret)
126 => self.add(Tilde)
63 => self.add(Question)
35 => self.add(Hash)
46 =>
if self.matches(46) {
if self.matches(46) {
self.add(DotDotDot)
} else {
self.add(DotDot)
}
} else {
self.add(Dot)
}
124 => if self.matches(124) { self.add(PipePipe) } else { self.add(Pipe) }
38 =>
if self.matches(38) {
self.add(AmpersandAmpersand)
} else {
self.add(Ampersand)
}
33 => if self.matches(61) { self.add(BangEqual) } else { self.add(Bang) }
61 => if self.matches(61) { self.add(EqualEqual) } else { self.add(Equal) }
60 =>
if self.matches(61) {
self.add(LessEqual)
} else if self.matches(60) {
self.add(LessLess)
} else {
self.add(Less)
}
62 =>
if self.matches(61) {
self.add(GreaterEqual)
} else if self.matches(62) {
self.add(GreaterGreater)
} else {
self.add(Greater)
}
47 =>
if self.matches(47) {
self.line_comment()
} else if self.matches(42) {
self.block_comment()
} else {
self.add(Slash)
}
34 =>
if self.peek() == 34 && self.peek_next() == 34 {
self.raw_string()
} else {
self.string()
}
10 => {
self.line = self.line + 1
self.column = 1
self.add(Newline)
}
13 =>
if self.peek() != 10 {
self.line = self.line + 1
self.column = 1
}
9 | 32 => ()
_ =>
if is_digit(code) {
self.number(code)
} else if is_alpha(code) {
self.identifier()
} else {
self.report(UnexpectedCharacter, "unexpected character")
}
}
}
///|
fn Lexer::scan(self : Lexer) -> ScanResult {
if self.current == 0 &&
self.peek() == 35 &&
self.peek_next() == 33 &&
self.source.length() > 2 &&
self.source.unsafe_get(2).to_int() == 47 {
while !self.at_end() && self.peek() != 10 {
ignore(self.advance())
}
}
while !self.at_end() {
self.start = self.current
self.start_line = self.line
self.start_column = self.column
self.scan_one()
}
self.start = self.current
self.start_line = self.line
self.start_column = self.column
self.add(Eof)
{ tokens: self.tokens, diagnostics: self.diagnostics, }
}
///|
/// Tokenize a Wren source string while collecting all recoverable scan errors.
pub fn scan(source : String) -> ScanResult {
let normalized = if source.length() >= 1 &&
source.unsafe_get(0).to_int() == 0xfeff {
source[1:].to_owned()
} else if source.length() >= 3 &&
source.unsafe_get(0).to_int() == 0xef &&
source.unsafe_get(1).to_int() == 0xbb &&
source.unsafe_get(2).to_int() == 0xbf {
source[3:].to_owned()
} else {
source
}
Lexer::new(normalized).scan()
}