use std::iter::Peekable;
use std::str::Chars;
fn is_ident_continue(ch: char) -> bool {
ch.is_ascii_alphanumeric() || ch == '_'
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Print, Set, Create, Add, Subtract, Multiply, Divide, Increment, Decrement,
Allocate, Free, Append, Copy, Clear,
Open, Read, Write, Close, Delete, Exists, Resize, Seek,
If, When, Then, Else, But, Otherwise, While, For, Each,
Repeat, Times, Break, Continue, Return, Exit,
With, Called, Modulo,
Flag, Parse, Required, Default,
Is, Are, Equals, Equal, Greater, Less, Than, Not, And, Or,
From, To, Between, In, Of, On, The, A, An, All, Treating,
Number, Float, Int, Text, Boolean, List, Map, True, False,
Buffer, File, Bytes, Size, Into, Reading, Writing, Appending, Standard, Input,
Even, Odd, Positive, Negative, Zero, Empty,
Nothing,
Apostrophe, Capacity, Descriptor, Modified, Accessed, Permissions,
Readable, Writable, Full, First, Last, Keys, Values, Absolute, Sign,
Error, Auto, Enable, Disable,
See, Library, Version,
Argument, Arguments, Environment, Variable, Count, Raw,
Wait, Sleep, Timer, Stop, Begin, Finish,
Get, Current, Time, Second, Seconds, Millisecond, Milliseconds,
Duration, Elapsed, Hour, Minute, Day, Month, Year, Unix,
Running, As,
BitAnd, BitOr, BitXor, BitNot, BitShiftLeft, BitShiftRight,
Byte, Element, Without,
IntegerLiteral(i64),
FloatLiteral(f64),
StringLiteral(String),
Identifier(String),
Period, Comma, Colon, OpenBracket, CloseBracket, OpenBrace, CloseBrace, Minus,
Newline, ParagraphBreak, EOF,
}
impl Token {
#[allow(dead_code)]
pub fn string_is_keyword(s: &str) -> Option<&'static str> {
let lower = s.to_lowercase();
match lower.as_str() {
"print" | "say" | "display" | "output" | "show" => Some("print"),
"set" | "assign" | "let" | "make" | "put" => Some("set"),
"create" | "declare" | "define" => Some("create"),
"add" | "plus" => Some("add"),
"subtract" | "minus" => Some("subtract"),
"multiply" | "times" => Some("multiply"),
"divide" | "over" => Some("divide"),
"increment" | "increase" => Some("increment"),
"decrement" | "decrease" => Some("decrement"),
"execute" => Some("execute"),
"allocate" => Some("allocate"),
"free" | "deallocate" | "release" => Some("free"),
"clear" => Some("clear"),
"modulo" | "mod" | "remainder" => Some("modulo"),
"if" => Some("if"),
"when" => Some("when"),
"then" => Some("then"),
"else" => Some("else"),
"but" => Some("but"),
"otherwise" => Some("otherwise"),
"while" => Some("while"),
"for" => Some("for"),
"each" => Some("each"),
"repeat" => Some("repeat"),
"break" => Some("break"),
"stop" => Some("stop"),
"continue" | "skip" => Some("continue"),
"return" | "give" | "respond" | "reply" => Some("return"),
"exit" | "quit" | "terminate" | "end" | "halt" | "abort" => Some("exit"),
"with" | "using" | "given" | "taking" => Some("with"),
"called" | "named" => Some("called"),
"parse" => Some("parse"),
"flag" => Some("flag"),
"required" => Some("required"),
"default" => Some("default"),
"is" | "equals" | "equal" | "==" => Some("is"),
"are" => Some("are"),
"greater" | "more" | "larger" | "bigger" | "higher" | "above" => Some("greater"),
"less" | "smaller" | "lower" | "below" | "fewer" => Some("less"),
"than" => Some("than"),
"not" | "!" => Some("not"),
"and" | "&&" => Some("and"),
"or" | "||" => Some("or"),
"from" | "starting" => Some("from"),
"to" | "up" => Some("to"),
"between" => Some("between"),
"in" | "inside" | "within" => Some("in"),
"of" => Some("of"),
"on" | "at" => Some("on"),
"the" => Some("the"),
"a" => Some("a"),
"an" => Some("an"),
"all" => Some("all"),
"treating" | "treat" => Some("treating"),
"number" | "numbers" => Some("number"),
"float" | "decimal" | "real" => Some("float"),
"int" | "integer" => Some("int"),
"text" | "string" | "message" => Some("text"),
"boolean" | "bool" => Some("boolean"),
"list" | "array" | "collection" => Some("list"),
"true" | "yes" => Some("true"),
"false" | "no" => Some("false"),
"buffer" => Some("buffer"),
"file" => Some("file"),
"bytes" => Some("bytes"),
"byte" => Some("byte"),
"size" | "length" => Some("size"),
"into" => Some("into"),
"reading" => Some("reading"),
"writing" => Some("writing"),
"appending" => Some("appending"),
"standard" => Some("standard"),
"input" => Some("input"),
"open" | "opened" => Some("open"),
"read" => Some("read"),
"write" => Some("write"),
"close" | "closed" => Some("close"),
"delete" | "remove" => Some("delete"),
"exists" | "exist" => Some("exists"),
"resize" | "reallocate" | "grow" | "shrink" => Some("resize"),
"seek" => Some("seek"),
"even" => Some("even"),
"odd" => Some("odd"),
"positive" => Some("positive"),
"negative" => Some("negative"),
"zero" => Some("zero"),
"empty" => Some("empty"),
"nothing" | "null" | "nil" => Some("nothing"),
"capacity" => Some("capacity"),
"descriptor" | "fd" => Some("descriptor"),
"modified" => Some("modified"),
"accessed" => Some("accessed"),
"permissions" | "perms" => Some("permissions"),
"readable" => Some("readable"),
"writable" => Some("writable"),
"full" => Some("full"),
"first" => Some("first"),
"last" => Some("last"),
"absolute" | "abs" => Some("absolute"),
"sign" => Some("sign"),
"error" => Some("error"),
"auto" | "automatic" => Some("auto"),
"enable" | "enabled" => Some("enable"),
"disable" | "disabled" => Some("disable"),
"see" | "import" | "include" | "require" => Some("see"),
"library" | "lib" => Some("library"),
"version" | "ver" => Some("version"),
"argument" | "arg" | "param" | "parameter" => Some("argument"),
"arguments" | "args" | "params" | "parameters" => Some("arguments"),
"environment" | "env" => Some("environment"),
"variable" | "var" => Some("variable"),
"count" => Some("count"),
"raw" => Some("raw"),
"wait" | "pause" => Some("wait"),
"sleep" | "delay" => Some("sleep"),
"timer" | "stopwatch" => Some("timer"),
"begin" => Some("begin"),
"finish" => Some("stop"),
"get" | "fetch" | "retrieve" => Some("get"),
"current" => Some("current"),
"time" => Some("time"),
"second" => Some("second"),
"seconds" => Some("seconds"),
"millisecond" => Some("millisecond"),
"milliseconds" | "ms" => Some("milliseconds"),
"duration" => Some("duration"),
"elapsed" => Some("elapsed"),
"hour" | "hours" => Some("hour"),
"minute" | "minutes" => Some("minute"),
"day" | "days" => Some("day"),
"month" | "months" => Some("month"),
"year" | "years" => Some("year"),
"unix" | "unixtime" | "timestamp" => Some("unix"),
"running" => Some("running"),
"as" => Some("as"),
_ => None,
}
}
pub fn as_keyword(&self) -> Option<&'static str> {
match self {
Token::Print => Some("print"),
Token::Set => Some("set"),
Token::Create => Some("create"),
Token::Add => Some("add"),
Token::Subtract => Some("subtract"),
Token::Multiply => Some("multiply"),
Token::Divide => Some("divide"),
Token::Increment => Some("increment"),
Token::Decrement => Some("decrement"),
Token::Allocate => Some("allocate"),
Token::Free => Some("free"),
Token::Append => Some("append"),
Token::Copy => Some("copy"),
Token::Clear => Some("clear"),
Token::Open => Some("open"),
Token::Read => Some("read"),
Token::Write => Some("write"),
Token::Close => Some("close"),
Token::Delete => Some("delete"),
Token::Exists => Some("exists"),
Token::Resize => Some("resize"),
Token::Seek => Some("seek"),
Token::If => Some("if"),
Token::When => Some("when"),
Token::Then => Some("then"),
Token::Else => Some("else"),
Token::But => Some("but"),
Token::Otherwise => Some("otherwise"),
Token::While => Some("while"),
Token::For => Some("for"),
Token::Each => Some("each"),
Token::Repeat => Some("repeat"),
Token::Times => Some("times"),
Token::Break => Some("break"),
Token::Continue => Some("continue"),
Token::Return => Some("return"),
Token::Exit => Some("exit"),
Token::With => Some("with"),
Token::Called => Some("called"),
Token::Modulo => Some("modulo"),
Token::Parse => Some("parse"),
Token::Flag => Some("flag"),
Token::Required => Some("required"),
Token::Default => Some("default"),
Token::Is => Some("is"),
Token::Are => Some("are"),
Token::Equals => Some("equals"),
Token::Equal => Some("equal"),
Token::Greater => Some("greater"),
Token::Less => Some("less"),
Token::Than => Some("than"),
Token::Not => Some("not"),
Token::And => Some("and"),
Token::Or => Some("or"),
Token::From => Some("from"),
Token::To => Some("to"),
Token::Between => Some("between"),
Token::In => Some("in"),
Token::Of => Some("of"),
Token::On => Some("on"),
Token::The => Some("the"),
Token::A => Some("a"),
Token::An => Some("an"),
Token::All => Some("all"),
Token::Treating => Some("treating"),
Token::Number => Some("number"),
Token::Float => Some("float"),
Token::Int => Some("int"),
Token::Text => Some("text"),
Token::Boolean => Some("boolean"),
Token::List => Some("list"),
Token::Map => Some("map"),
Token::True => Some("true"),
Token::False => Some("false"),
Token::Buffer => Some("buffer"),
Token::File => Some("file"),
Token::Bytes => Some("bytes"),
Token::Size => Some("size"),
Token::Into => Some("into"),
Token::Reading => Some("reading"),
Token::Writing => Some("writing"),
Token::Appending => Some("appending"),
Token::Standard => Some("standard"),
Token::Input => Some("input"),
Token::Even => Some("even"),
Token::Odd => Some("odd"),
Token::Positive => Some("positive"),
Token::Negative => Some("negative"),
Token::Zero => Some("zero"),
Token::Empty => Some("empty"),
Token::Nothing => Some("nothing"),
Token::Apostrophe => None, Token::Capacity => Some("capacity"),
Token::Descriptor => Some("descriptor"),
Token::Modified => Some("modified"),
Token::Accessed => Some("accessed"),
Token::Permissions => Some("permissions"),
Token::Readable => Some("readable"),
Token::Writable => Some("writable"),
Token::Full => Some("full"),
Token::First => Some("first"),
Token::Last => Some("last"),
Token::Keys => Some("keys"),
Token::Values => Some("values"),
Token::Absolute => Some("absolute"),
Token::Sign => Some("sign"),
Token::Error => Some("error"),
Token::Auto => Some("auto"),
Token::Enable => Some("enable"),
Token::Disable => Some("disable"),
Token::See => Some("see"),
Token::Library => Some("library"),
Token::Version => Some("version"),
Token::Argument => Some("argument"),
Token::Arguments => Some("arguments"),
Token::Environment => Some("environment"),
Token::Variable => Some("variable"),
Token::Count => Some("count"),
Token::Raw => Some("raw"),
Token::Wait => Some("wait"),
Token::Sleep => Some("sleep"),
Token::Timer => Some("timer"),
Token::Stop => Some("stop"),
Token::Begin => Some("begin"),
Token::Finish => Some("finish"),
Token::Get => Some("get"),
Token::Current => Some("current"),
Token::Time => Some("time"),
Token::Second => Some("second"),
Token::Seconds => Some("seconds"),
Token::Millisecond => Some("millisecond"),
Token::Milliseconds => Some("milliseconds"),
Token::Duration => Some("duration"),
Token::Elapsed => Some("elapsed"),
Token::Hour => Some("hour"),
Token::Minute => Some("minute"),
Token::Day => Some("day"),
Token::Month => Some("month"),
Token::Year => Some("year"),
Token::Unix => Some("unix"),
Token::Running => Some("running"),
Token::As => Some("as"),
Token::BitAnd => Some("bit-and"),
Token::BitOr => Some("bit-or"),
Token::BitXor => Some("bit-xor"),
Token::BitNot => Some("bit-not"),
Token::BitShiftLeft => Some("bit-shift-left"),
Token::BitShiftRight => Some("bit-shift-right"),
Token::Byte => Some("byte"),
Token::Element => Some("element"),
Token::Without => Some("without"),
Token::IntegerLiteral(_) => None,
Token::FloatLiteral(_) => None,
Token::StringLiteral(_) => None,
Token::Identifier(_) => None,
Token::Period => None,
Token::Comma => None,
Token::Colon => None,
Token::OpenBracket => None,
Token::CloseBracket => None,
Token::OpenBrace => None,
Token::CloseBrace => None,
Token::Minus => None,
Token::Newline => None,
Token::ParagraphBreak => None,
Token::EOF => None,
}
}
}
#[derive(Debug, Clone)]
pub struct TokenInfo {
pub token: Token,
pub line: usize,
pub column: usize,
}
pub struct Lexer<'a> {
input: Peekable<Chars<'a>>,
line: usize,
column: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Lexer {
input: input.chars().peekable(),
line: 1,
column: 1,
}
}
fn advance(&mut self) -> Option<char> {
let ch = self.input.next();
if let Some(c) = ch {
if c == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
}
ch
}
fn peek(&mut self) -> Option<&char> {
self.input.peek()
}
fn skip_whitespace(&mut self) {
while let Some(&ch) = self.peek() {
if ch == ' ' || ch == '\t' || ch == '\r' {
self.advance();
} else {
break;
}
}
}
fn skip_comment(&mut self) {
let mut depth = 1;
while depth > 0 {
match self.advance() {
Some('(') => depth += 1,
Some(')') => depth -= 1,
None => break, _ => {} }
}
}
fn read_string(&mut self) -> String {
let mut result = String::new();
while let Some(&ch) = self.peek() {
if ch == '"' {
self.advance();
break;
} else if ch == '\\' {
self.advance();
if let Some(&escaped) = self.peek() {
match escaped {
'n' => result.push('\n'),
't' => result.push('\t'),
'r' => result.push('\r'),
'\\' => result.push('\\'),
'"' => result.push('"'),
_ => result.push(escaped),
}
self.advance();
}
} else {
result.push(ch);
self.advance();
}
}
result
}
fn read_single_quoted_string(&mut self) -> String {
let mut result = String::new();
while let Some(&ch) = self.peek() {
if ch == '\'' {
self.advance();
break;
} else if ch == '\\' {
self.advance();
if let Some(&escaped) = self.peek() {
match escaped {
'n' => result.push('\n'),
't' => result.push('\t'),
'r' => result.push('\r'),
'\\' => result.push('\\'),
'\'' => result.push('\''),
_ => result.push(escaped),
}
self.advance();
}
} else {
result.push(ch);
self.advance();
}
}
result
}
fn is_char_literal(&self) -> bool {
let mut input = self.input.clone();
if let Some(&first) = input.peek() {
input.next();
if first == '\\' {
input.next(); if let Some(&close) = input.peek() {
return close == '\'';
}
} else {
if let Some(&close) = input.peek() {
return close == '\'';
}
}
}
false
}
fn is_single_quoted_identifier(&self) -> bool {
let mut input = self.input.clone();
if let Some(&first) = input.peek() {
if first == 's' || first == 'S' {
input.next();
if let Some(&second) = input.peek() {
if second.is_whitespace() || second == '.' || second == ',' || second == '\'' {
return false; }
} else {
return false; }
}
}
let mut input = self.input.clone();
let mut count = 0;
while let Some(&ch) = input.peek() {
if ch == '\'' {
return count > 0;
} else if ch == '\n' {
return false; }
input.next();
count += 1;
}
false
}
fn peek_is_possessive_s_after_quote(&mut self) -> bool {
let mut input = self.input.clone();
if let Some(&s) = input.peek() {
if s == 's' || s == 'S' {
input.next();
return match input.peek() {
None => true, Some(&after) => !is_ident_continue(after),
};
}
}
false
}
fn read_number(&mut self, first: char) -> Token {
if first == '0' {
if let Some(&next) = self.peek() {
if next == 'x' || next == 'X' {
self.advance(); return self.read_hex_number();
} else if next == 'b' || next == 'B' {
self.advance(); return self.read_binary_number();
}
}
}
let mut num = String::from(first);
let mut is_float = false;
while let Some(&ch) = self.peek() {
if ch.is_ascii_digit() {
num.push(ch);
self.advance();
} else if ch == '.' && !is_float {
let mut chars = self.input.clone();
chars.next(); if let Some(&next) = chars.peek() {
if next.is_ascii_digit() {
is_float = true;
num.push(ch);
self.advance();
} else {
break;
}
} else {
break;
}
} else {
break;
}
}
if is_float {
Token::FloatLiteral(num.parse().unwrap_or(0.0))
} else {
Token::IntegerLiteral(num.parse().unwrap_or(0))
}
}
fn read_hex_number(&mut self) -> Token {
let mut num = String::new();
while let Some(&ch) = self.peek() {
if ch.is_ascii_hexdigit() {
num.push(ch);
self.advance();
} else {
break;
}
}
if num.is_empty() {
Token::IntegerLiteral(0)
} else {
Token::IntegerLiteral(i64::from_str_radix(&num, 16).unwrap_or(0))
}
}
fn read_binary_number(&mut self) -> Token {
let mut num = String::new();
while let Some(&ch) = self.peek() {
if ch == '0' || ch == '1' {
num.push(ch);
self.advance();
} else {
break;
}
}
if num.is_empty() {
Token::IntegerLiteral(0)
} else {
Token::IntegerLiteral(i64::from_str_radix(&num, 2).unwrap_or(0))
}
}
fn read_char_literal(&mut self) -> Token {
let ch = match self.advance() {
Some('\\') => {
match self.advance() {
Some('n') => '\n',
Some('t') => '\t',
Some('r') => '\r',
Some('\\') => '\\',
Some('\'') => '\'',
Some('0') => '\0',
Some(c) => c,
None => '\0',
}
}
Some(c) => c,
None => '\0',
};
if let Some(&'\'') = self.peek() {
self.advance();
}
Token::IntegerLiteral(ch as i64)
}
fn read_word(&mut self, first: char) -> Token {
let mut word = String::from(first);
while let Some(&ch) = self.peek() {
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
word.push(ch);
self.advance();
} else {
break;
}
}
match word.to_lowercase().as_str() {
"print" | "prints" | "display" | "show" => Token::Print,
"set" | "store" | "assign" => Token::Set,
"create" | "make" | "define" => Token::Create,
"add" | "plus" => Token::Add,
"subtract" | "minus" => Token::Subtract,
"multiply" => Token::Multiply,
"divide" => Token::Divide,
"increment" => Token::Increment,
"decrement" => Token::Decrement,
"allocate" => Token::Allocate,
"free" | "release" | "deallocate" => Token::Free,
"append" | "push" => Token::Append,
"copy" => Token::Copy,
"clear" => Token::Clear,
"if" => Token::If,
"when" => Token::When,
"then" => Token::Then,
"else" => Token::Else,
"but" => Token::But,
"otherwise" => Token::Otherwise,
"while" => Token::While,
"for" => Token::For,
"each" => Token::Each,
"repeat" => Token::Repeat,
"times" => Token::Times,
"break" => Token::Break,
"stop" => Token::Stop,
"exit" | "quit" | "terminate" => Token::Exit,
"continue" | "skip" => Token::Continue,
"return" | "returns" | "give" => Token::Return,
"to" => Token::To,
"with" => Token::With,
"called" | "named" => Token::Called,
"modulo" | "mod" | "remainder" => Token::Modulo,
"parse" => Token::Parse,
"flag" => Token::Flag,
"flags" => Token::Identifier("flags".to_string()),
"required" => Token::Required,
"default" => Token::Default,
"is" | "it's" => Token::Is,
"it" => Token::Identifier("it".to_string()),
"are" | "they're" => Token::Are,
"equals" | "equal" => Token::Equals,
"greater" | "more" | "bigger" | "larger" => Token::Greater,
"less" | "fewer" | "smaller" => Token::Less,
"than" => Token::Than,
"not" | "isn't" | "aren't" | "doesn't" | "don't" => Token::Not,
"and" => Token::And,
"or" => Token::Or,
"from" | "starting" => Token::From,
"up" => Token::To,
"between" => Token::Between,
"in" | "inside" | "within" => Token::In,
"of" => Token::Of,
"on" | "at" => Token::On,
"the" => Token::The,
"a" => Token::A,
"an" => Token::An,
"all" => Token::All,
"number" | "numbers" => Token::Number,
"float" | "decimal" | "real" => Token::Float,
"int" | "integer" => Token::Int,
"text" | "string" | "message" => Token::Text,
"boolean" | "bool" => Token::Boolean,
"list" | "array" | "collection" => Token::List,
"map" | "dictionary" => Token::Map,
"true" | "yes" => Token::True,
"false" | "no" => Token::False,
"even" => Token::Even,
"odd" => Token::Odd,
"positive" => Token::Positive,
"negative" => Token::Negative,
"zero" => Token::Zero,
"empty" => Token::Empty,
"nothing" | "null" | "nil" => Token::Nothing,
"open" | "opened" => Token::Open,
"read" => Token::Read,
"write" => Token::Write,
"close" | "closed" => Token::Close,
"delete" | "remove" => Token::Delete,
"exists" | "exist" => Token::Exists,
"resize" | "reallocate" | "grow" | "shrink" => Token::Resize,
"seek" => Token::Seek,
"buffer" => Token::Buffer,
"file" => Token::File,
"bytes" => Token::Bytes,
"size" | "length" => Token::Size,
"capacity" => Token::Capacity,
"into" => Token::Into,
"reading" => Token::Reading,
"writing" => Token::Writing,
"appending" => Token::Appending,
"standard" => Token::Standard,
"input" => Token::Input,
"error" => Token::Error,
"auto" | "automatic" => Token::Auto,
"enable" | "enabled" => Token::Enable,
"disable" | "disabled" => Token::Disable,
"descriptor" | "fd" => Token::Descriptor,
"modified" => Token::Modified,
"accessed" => Token::Accessed,
"permissions" | "perms" => Token::Permissions,
"readable" => Token::Readable,
"writable" => Token::Writable,
"full" => Token::Full,
"first" => Token::First,
"last" => Token::Last,
"keys" => Token::Keys,
"values" => Token::Values,
"absolute" | "abs" => Token::Absolute,
"sign" => Token::Sign,
"see" | "import" | "include" | "require" => Token::See,
"library" | "lib" => Token::Library,
"version" | "ver" => Token::Version,
"argument" | "arg" | "param" | "parameter" => Token::Argument,
"arguments" | "args" | "params" | "parameters" => Token::Arguments,
"environment" | "env" => Token::Environment,
"variable" | "var" => Token::Variable,
"count" => Token::Count,
"raw" => Token::Raw,
"treating" | "treat" => Token::Treating,
"wait" | "pause" => Token::Wait,
"sleep" | "delay" => Token::Sleep,
"timer" | "stopwatch" => Token::Timer,
"start" => Token::Identifier("start".to_string()),
"begin" => Token::Begin,
"finish" => Token::Finish,
"get" | "fetch" | "retrieve" => Token::Get,
"current" => Token::Current,
"time" => Token::Time,
"second" => Token::Second,
"seconds" => Token::Seconds,
"millisecond" => Token::Millisecond,
"milliseconds" | "ms" => Token::Milliseconds,
"duration" => Token::Duration,
"elapsed" => Token::Elapsed,
"hour" | "hours" => Token::Hour,
"minute" | "minutes" => Token::Minute,
"day" | "days" => Token::Day,
"month" | "months" => Token::Month,
"year" | "years" => Token::Year,
"unix" | "unixtime" | "timestamp" => Token::Unix,
"running" => Token::Running,
"as" => Token::As,
"bit-and" => Token::BitAnd,
"bit-or" => Token::BitOr,
"bit-xor" => Token::BitXor,
"bit-not" => Token::BitNot,
"bit-shift-left" => Token::BitShiftLeft,
"bit-shift-right" => Token::BitShiftRight,
"byte" => Token::Byte,
"element" => Token::Element,
"without" => Token::Without,
_ => Token::Identifier(word),
}
}
pub fn tokenize(&mut self) -> Vec<TokenInfo> {
let mut tokens = Vec::new();
loop {
self.skip_whitespace();
let line = self.line;
let column = self.column;
let token = match self.advance() {
None => Token::EOF,
Some(ch) => match ch {
'\n' => {
let mut newline_count = 1;
while let Some(&next) = self.peek() {
if next == '\n' {
self.advance();
newline_count += 1;
} else if next == ' ' || next == '\t' || next == '\r' {
self.advance();
} else {
break;
}
}
if newline_count >= 2 {
Token::ParagraphBreak
} else {
Token::Newline
}
}
'.' => Token::Period,
',' => Token::Comma,
':' => Token::Colon,
'(' => {
self.skip_comment();
continue;
}
')' => continue, '[' => Token::OpenBracket,
']' => Token::CloseBracket,
'{' => Token::OpenBrace,
'}' => Token::CloseBrace,
'-' => Token::Minus,
'\'' => {
if self.is_char_literal() {
self.read_char_literal()
} else if self.is_single_quoted_identifier() {
let content = self.read_single_quoted_string();
tokens.push(TokenInfo {
token: Token::Identifier(content),
line,
column,
});
if self.peek_is_possessive_s_after_quote() {
tokens.push(TokenInfo {
token: Token::Apostrophe,
line: self.line,
column: self.column,
});
}
continue;
} else {
Token::Apostrophe
}
}
'"' => Token::StringLiteral(self.read_string()),
c if c.is_ascii_digit() => self.read_number(c),
c if c.is_alphabetic() || c == '_' => self.read_word(c),
_ => continue,
}
};
let is_eof = token == Token::EOF;
tokens.push(TokenInfo { token, line, column });
if is_eof {
break;
}
}
tokens
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tokens_of(input: &str) -> Vec<Token> {
let mut lexer = Lexer::new(input);
lexer
.tokenize()
.into_iter()
.map(|t| t.token)
.filter(|t| *t != Token::EOF)
.collect()
}
#[test]
fn single_char_between_single_quotes_is_a_character_literal() {
assert_eq!(tokens_of("'A'"), vec![Token::IntegerLiteral(65)]);
}
#[test]
fn possessive_single_apostrophe_form_lexes_identifier_then_s() {
assert_eq!(
tokens_of("'my nums's length"),
vec![
Token::Identifier("my nums".to_string()),
Token::Apostrophe,
Token::Identifier("s".to_string()),
Token::Size,
]
);
}
#[test]
fn possessive_doubled_apostrophe_form_lexes_identically() {
assert_eq!(
tokens_of("'my nums''s length"),
tokens_of("'my nums's length")
);
}
#[test]
fn underscore_prefix_is_preserved_in_identifier() {
assert_eq!(
tokens_of("_str_eq"),
vec![Token::Identifier("_str_eq".to_string())]
);
assert_eq!(
tokens_of("my_helper"),
vec![Token::Identifier("my_helper".to_string())]
);
}
}