Simple identifier lexer

This commit is contained in:
Aleksey Kladov 2017-12-29 23:33:04 +03:00
parent 15af7ad36c
commit 171baf4c48
8 changed files with 146 additions and 19 deletions

View file

@ -4,6 +4,8 @@ version = "0.1.0"
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"] authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
[dependencies] [dependencies]
unicode-xid = "0.1.0"
serde = "1.0.26" serde = "1.0.26"
serde_derive = "1.0.26" serde_derive = "1.0.26"
file = "1.1.1" file = "1.1.1"

View file

@ -1,10 +0,0 @@
use {Token, TextUnit};
use syntax_kinds::*;
pub fn next_token(text: &str) -> Token {
let c = text.chars().next().unwrap();
Token {
kind: IDENT,
len: TextUnit::len_of_char(c),
}
}

64
src/lexer/mod.rs Normal file
View file

@ -0,0 +1,64 @@
use unicode_xid::UnicodeXID;
use {Token, SyntaxKind};
use syntax_kinds::*;
mod ptr;
use self::ptr::Ptr;
pub fn next_token(text: &str) -> Token {
assert!(!text.is_empty());
let mut ptr = Ptr::new(text);
let c = ptr.bump().unwrap();
let kind = next_token_inner(c, &mut ptr);
let len = ptr.into_len();
Token { kind, len }
}
fn next_token_inner(c: char, ptr: &mut Ptr) -> SyntaxKind {
// Note: r as in r" or r#" is part of a raw string literal,
// b as in b' is part of a byte literal.
// They are not identifiers, and are handled further down.
let ident_start = ident_start(c) && !string_literal_start(c, ptr.next(), ptr.nnext());
if ident_start {
loop {
match ptr.next() {
Some(c) if ident_continue(c) => {
ptr.bump();
},
_ => break,
}
}
IDENT
} else {
WHITESPACE
}
}
fn ident_start(c: char) -> bool {
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_start(c))
}
fn ident_continue(c: char) -> bool {
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_continue(c))
}
fn string_literal_start(c: char, c1: Option<char>, c2: Option<char>) -> bool {
match (c, c1, c2) {
('r', Some('"'), _) |
('r', Some('#'), _) |
('b', Some('"'), _) |
('b', Some('\''), _) |
('b', Some('r'), Some('"')) |
('b', Some('r'), Some('#')) => true,
_ => false
}
}

38
src/lexer/ptr.rs Normal file
View file

@ -0,0 +1,38 @@
use {TextUnit};
use std::str::Chars;
pub(crate) struct Ptr<'s> {
text: &'s str,
len: TextUnit,
}
impl<'s> Ptr<'s> {
pub fn new(text: &'s str) -> Ptr<'s> {
Ptr { text, len: TextUnit::new(0) }
}
pub fn into_len(self) -> TextUnit {
self.len
}
pub fn next(&self) -> Option<char> {
self.chars().next()
}
pub fn nnext(&self) -> Option<char> {
let mut chars = self.chars();
chars.next()?;
chars.next()
}
pub fn bump(&mut self) -> Option<char> {
let ch = self.chars().next()?;
self.len += TextUnit::len_of_char(ch);
Some(ch)
}
fn chars(&self) -> Chars {
self.text[self.len.0 as usize ..].chars()
}
}

View file

@ -1,3 +1,5 @@
extern crate unicode_xid;
mod text; mod text;
mod tree; mod tree;
mod lexer; mod lexer;

View file

@ -1,7 +1,10 @@
use std::fmt; use std::fmt;
use std::ops;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextUnit(u32); pub struct TextUnit(
pub(crate) u32
);
impl TextUnit { impl TextUnit {
pub fn len_of_char(c: char) -> TextUnit { pub fn len_of_char(c: char) -> TextUnit {
@ -30,3 +33,29 @@ impl From<TextUnit> for u32 {
tu.0 tu.0
} }
} }
impl ops::Add<TextUnit> for TextUnit {
type Output = TextUnit;
fn add(self, rhs: TextUnit) -> TextUnit {
TextUnit(self.0 + rhs.0)
}
}
impl ops::AddAssign<TextUnit> for TextUnit {
fn add_assign(&mut self, rhs: TextUnit) {
self.0 += rhs.0
}
}
impl ops::Sub<TextUnit> for TextUnit {
type Output = TextUnit;
fn sub(self, rhs: TextUnit) -> TextUnit {
TextUnit(self.0 - rhs.0)
}
}
impl ops::SubAssign<TextUnit> for TextUnit {
fn sub_assign(&mut self, rhs: TextUnit) {
self.0 -= rhs.0
}
}

View file

@ -41,13 +41,15 @@ fn lexer_test_case(path: &Path) {
dump_tokens(&tokens) dump_tokens(&tokens)
}; };
let expected = file::get_text(&path.with_extension("txt")).unwrap(); let expected = file::get_text(&path.with_extension("txt")).unwrap();
let expected = expected.as_str();
assert_diff!( let actual = actual.as_str();
expected.as_str(), if expected == actual {
actual.as_str(), return
"\n", }
0 if expected.trim() == actual.trim() {
) panic!("Whitespace difference!")
}
assert_diff!(expected, actual, "\n", 0)
} }
fn tokenize(text: &str) -> Vec<Token> { fn tokenize(text: &str) -> Vec<Token> {