[fine] Starting to parse (ugh)
This commit is contained in:
parent
7fccab8f59
commit
ece5576fb2
3 changed files with 534 additions and 185 deletions
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod parser;
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
|
|
|
||||||
360
oden-script/src/parser.rs
Normal file
360
oden-script/src/parser.rs
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
use crate::tokens::{Token, TokenKind, Tokens};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub struct SyntaxError {
|
||||||
|
pub line: usize,
|
||||||
|
pub column: usize,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxError {
|
||||||
|
pub fn new(line: usize, column: usize, message: String) -> Self {
|
||||||
|
SyntaxError {
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SyntaxError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}: {}", self.line, self.column, self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SyntaxError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}: {}", self.line, self.column, self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Literal {
|
||||||
|
Float64(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UnaryOp {
|
||||||
|
Negate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum BinaryOp {
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Mutiply,
|
||||||
|
Divide,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Expr {
|
||||||
|
Literal(Literal),
|
||||||
|
Unary(UnaryOp, ExprRef),
|
||||||
|
Binary(BinaryOp, ExprRef, ExprRef),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExprRef(Option<usize>);
|
||||||
|
|
||||||
|
impl ExprRef {
|
||||||
|
pub fn error() -> Self {
|
||||||
|
ExprRef(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyntaxTree {
|
||||||
|
pub errors: Vec<SyntaxError>,
|
||||||
|
expressions: Vec<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxTree {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SyntaxTree {
|
||||||
|
errors: Vec::new(),
|
||||||
|
expressions: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_error(&mut self, error: SyntaxError) {
|
||||||
|
self.errors.push(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_expr(&mut self, expr: Expr) -> ExprRef {
|
||||||
|
let index = self.expressions.len();
|
||||||
|
self.expressions.push(expr);
|
||||||
|
ExprRef(Some(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_expr(&self, expr: &ExprRef) -> String {
|
||||||
|
match expr.0 {
|
||||||
|
Some(idx) => {
|
||||||
|
let expr = &self.expressions[idx];
|
||||||
|
match expr {
|
||||||
|
Expr::Literal(lit) => match lit {
|
||||||
|
Literal::Float64(f) => f.to_string(),
|
||||||
|
},
|
||||||
|
Expr::Unary(op, e) => {
|
||||||
|
let op = match op {
|
||||||
|
UnaryOp::Negate => "-",
|
||||||
|
};
|
||||||
|
format!("({op} {})", self.dump_expr(e))
|
||||||
|
}
|
||||||
|
Expr::Binary(op, l, r) => {
|
||||||
|
let op = match op {
|
||||||
|
BinaryOp::Add => "+",
|
||||||
|
BinaryOp::Subtract => "-",
|
||||||
|
BinaryOp::Mutiply => "*",
|
||||||
|
BinaryOp::Divide => "/",
|
||||||
|
BinaryOp::And => "and",
|
||||||
|
BinaryOp::Or => "or",
|
||||||
|
};
|
||||||
|
format!("({op} {} {})", self.dump_expr(l), self.dump_expr(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => "<|EOF|>".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BINDING POWERS. When parsing expressions we only accept expressions that
|
||||||
|
// meet a minimum binding power. (This is like "precedence" but I just super
|
||||||
|
// don't like that terminology.)
|
||||||
|
const ASSIGNMENT_POWER: u8 = 0; // =
|
||||||
|
const OR_POWER: u8 = 1; // or
|
||||||
|
const AND_POWER: u8 = 2; // and
|
||||||
|
const EQUALITY_POWER: u8 = 3; // == !=
|
||||||
|
const COMPARISON_POWER: u8 = 4; // < > <= >=
|
||||||
|
const TERM_POWER: u8 = 5; // + -
|
||||||
|
const FACTOR_POWER: u8 = 6; // * /
|
||||||
|
const UNARY_POWER: u8 = 7; // ! -
|
||||||
|
|
||||||
|
// const CALL_POWER: u8 = 8; // . ()
|
||||||
|
// const PRIMARY_POWER: u8 = 9;
|
||||||
|
|
||||||
|
fn token_power<'a>(token: &Option<Token<'a>>) -> Option<u8> {
|
||||||
|
let token = match token {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match token.kind() {
|
||||||
|
TokenKind::Equal => Some(ASSIGNMENT_POWER),
|
||||||
|
TokenKind::Or => Some(OR_POWER),
|
||||||
|
TokenKind::And => Some(AND_POWER),
|
||||||
|
TokenKind::EqualEqual | TokenKind::BangEqual => Some(EQUALITY_POWER),
|
||||||
|
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
||||||
|
Some(COMPARISON_POWER)
|
||||||
|
}
|
||||||
|
TokenKind::Plus | TokenKind::Minus => Some(TERM_POWER),
|
||||||
|
TokenKind::Star | TokenKind::Slash => Some(FACTOR_POWER),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Parser<'a> {
|
||||||
|
tokens: Tokens<'a>,
|
||||||
|
tree: SyntaxTree,
|
||||||
|
current: Option<Token<'a>>,
|
||||||
|
previous: Option<Token<'a>>,
|
||||||
|
|
||||||
|
panic_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
pub fn new(source: &'a str) -> Self {
|
||||||
|
let mut parser = Parser {
|
||||||
|
tokens: Tokens::new(source),
|
||||||
|
tree: SyntaxTree::new(),
|
||||||
|
current: None,
|
||||||
|
previous: None,
|
||||||
|
panic_mode: false,
|
||||||
|
};
|
||||||
|
parser.advance();
|
||||||
|
parser
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(mut self) -> (SyntaxTree, ExprRef) {
|
||||||
|
let expr = self.expression();
|
||||||
|
self.consume(None, "expected end of expression");
|
||||||
|
(self.tree, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression(&mut self) -> ExprRef {
|
||||||
|
self.expression_with_power(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression_with_power(&mut self, minimum_power: u8) -> ExprRef {
|
||||||
|
self.advance();
|
||||||
|
let mut expr = self.prefix_expression();
|
||||||
|
loop {
|
||||||
|
let power = match token_power(&self.current) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => break, // EOF, end of expression?
|
||||||
|
};
|
||||||
|
|
||||||
|
if power < minimum_power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.advance();
|
||||||
|
expr = self.infix_expression(power, expr);
|
||||||
|
}
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix_expression(&mut self) -> ExprRef {
|
||||||
|
let token = self.previous.as_ref();
|
||||||
|
match token {
|
||||||
|
Some(token) => match token.kind() {
|
||||||
|
TokenKind::LeftParen => self.grouping(),
|
||||||
|
TokenKind::Number => self.number(),
|
||||||
|
TokenKind::Minus => self.unary(),
|
||||||
|
_ => {
|
||||||
|
self.error("expected an expression");
|
||||||
|
ExprRef::error()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.error("expected an expression");
|
||||||
|
ExprRef::error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infix_expression(&mut self, power: u8, left: ExprRef) -> ExprRef {
|
||||||
|
let kind = self.previous.as_ref().unwrap().kind();
|
||||||
|
match kind {
|
||||||
|
TokenKind::Plus | TokenKind::Minus | TokenKind::Star | TokenKind::Slash => {
|
||||||
|
self.binary(power, left)
|
||||||
|
}
|
||||||
|
_ => panic!("Unknown infix operator, dispatch error?"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number(&mut self) -> ExprRef {
|
||||||
|
let token = self.previous.as_ref().unwrap();
|
||||||
|
// What kind is it? For now let's just ... make it good.
|
||||||
|
|
||||||
|
match token.as_str().parse::<f64>() {
|
||||||
|
Ok(v) => self.tree.add_expr(Expr::Literal(Literal::Float64(v))),
|
||||||
|
Err(e) => {
|
||||||
|
self.error(format!("invalid f64: {e}"));
|
||||||
|
ExprRef::error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grouping(&mut self) -> ExprRef {
|
||||||
|
let result = self.number();
|
||||||
|
self.consume(
|
||||||
|
Some(TokenKind::RightParen),
|
||||||
|
"expected ')' after an expression",
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary(&mut self) -> ExprRef {
|
||||||
|
let kind = self.previous.as_ref().unwrap().kind();
|
||||||
|
let expr = self.expression_with_power(UNARY_POWER);
|
||||||
|
let op = match kind {
|
||||||
|
TokenKind::Minus => UnaryOp::Negate,
|
||||||
|
_ => panic!("unsuitable unary: {:?}: no op", kind),
|
||||||
|
};
|
||||||
|
self.tree.add_expr(Expr::Unary(op, expr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary(&mut self, power: u8, left: ExprRef) -> ExprRef {
|
||||||
|
let right = self.expression_with_power(power + 1);
|
||||||
|
let op = match self.previous.as_ref().unwrap().kind() {
|
||||||
|
TokenKind::Plus => BinaryOp::Add,
|
||||||
|
TokenKind::Minus => BinaryOp::Subtract,
|
||||||
|
TokenKind::Star => BinaryOp::Mutiply,
|
||||||
|
TokenKind::Slash => BinaryOp::Divide,
|
||||||
|
TokenKind::And => BinaryOp::And,
|
||||||
|
TokenKind::Or => BinaryOp::Or,
|
||||||
|
_ => panic!("unsuitable binary: {:?}: no op", self.previous),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tree.add_expr(Expr::Binary(op, left, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(&mut self) {
|
||||||
|
self.previous = self.current.take();
|
||||||
|
loop {
|
||||||
|
self.current = self.tokens.next();
|
||||||
|
match &self.current {
|
||||||
|
Some(token) if token.kind() == TokenKind::Error => {
|
||||||
|
self.error_at_current(token.clone())
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume(&mut self, kind: Option<TokenKind>, error: &str) {
|
||||||
|
match (&self.current, kind) {
|
||||||
|
(Some(token), Some(kind)) if token.kind() == kind => self.advance(),
|
||||||
|
(None, None) => (),
|
||||||
|
_ => {
|
||||||
|
self.error_at_current(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error<T>(&mut self, message: T)
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.error_at(self.previous.clone(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_at_current<T>(&mut self, message: T)
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.error_at(self.current.clone(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_at<T>(&mut self, token: Option<Token<'a>>, message: T)
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
if self.panic_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.panic_mode = true;
|
||||||
|
|
||||||
|
let message: String = message.into();
|
||||||
|
let (line, column) = self.tokens.token_position(&token);
|
||||||
|
let mut final_message = "Error ".to_string();
|
||||||
|
match token {
|
||||||
|
None => final_message.push_str("at end"),
|
||||||
|
Some(t) => {
|
||||||
|
if t.kind() != TokenKind::Error {
|
||||||
|
final_message.push_str("at '");
|
||||||
|
final_message.push_str(t.as_str());
|
||||||
|
final_message.push_str("'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final_message.push_str(": ");
|
||||||
|
final_message.push_str(&message);
|
||||||
|
|
||||||
|
self.tree
|
||||||
|
.add_error(SyntaxError::new(line, column, final_message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn number_expressions() {
|
||||||
|
// How am I going to test this?
|
||||||
|
let (tree, expr) = Parser::new("23.5").parse();
|
||||||
|
assert_eq!(Vec::<SyntaxError>::new(), tree.errors);
|
||||||
|
assert_eq!("23.5", tree.dump_expr(&expr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum TokenKind<'a> {
|
pub enum TokenKind {
|
||||||
LeftBrace,
|
LeftBrace,
|
||||||
RightBrace,
|
RightBrace,
|
||||||
LeftBracket,
|
LeftBracket,
|
||||||
|
|
@ -23,9 +23,9 @@ pub enum TokenKind<'a> {
|
||||||
Less,
|
Less,
|
||||||
LessEqual,
|
LessEqual,
|
||||||
|
|
||||||
Identifier(&'a str), // TODO
|
Identifier,
|
||||||
String(&'a str),
|
String,
|
||||||
Number(&'a str),
|
Number,
|
||||||
|
|
||||||
And,
|
And,
|
||||||
Async,
|
Async,
|
||||||
|
|
@ -47,80 +47,54 @@ pub enum TokenKind<'a> {
|
||||||
While,
|
While,
|
||||||
Yield,
|
Yield,
|
||||||
|
|
||||||
Error(String),
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Token<'a> {
|
pub struct Token<'a> {
|
||||||
kind: TokenKind<'a>,
|
kind: TokenKind,
|
||||||
start: usize,
|
start: usize,
|
||||||
|
value: Result<&'a str, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Token<'a> {
|
impl<'a> Token<'a> {
|
||||||
pub fn new(start: usize, kind: TokenKind<'a>) -> Self {
|
pub fn new(kind: TokenKind, start: usize, value: &'a str) -> Self {
|
||||||
Token { kind, start }
|
Token {
|
||||||
|
kind,
|
||||||
|
start,
|
||||||
|
value: Ok(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error(start: usize, message: String) -> Self {
|
||||||
|
Token {
|
||||||
|
kind: TokenKind::Error,
|
||||||
|
start,
|
||||||
|
value: Err(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> TokenKind {
|
||||||
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str<'b>(&'b self) -> &'a str
|
pub fn as_str<'b>(&'b self) -> &'a str
|
||||||
where
|
where
|
||||||
'b: 'a,
|
'b: 'a,
|
||||||
{
|
{
|
||||||
use TokenKind::*;
|
match &self.value {
|
||||||
match &self.kind {
|
Ok(v) => v,
|
||||||
LeftBrace => "{",
|
Err(e) => &e,
|
||||||
RightBrace => "}",
|
|
||||||
LeftBracket => "[",
|
|
||||||
RightBracket => "]",
|
|
||||||
|
|
||||||
LeftParen => "(",
|
|
||||||
RightParen => ")",
|
|
||||||
Comma => ",",
|
|
||||||
Dot => ".",
|
|
||||||
Minus => "-",
|
|
||||||
|
|
||||||
Plus => "+",
|
|
||||||
Semicolon => ";",
|
|
||||||
Slash => "/",
|
|
||||||
Star => "*",
|
|
||||||
|
|
||||||
Bang => "+",
|
|
||||||
BangEqual => "!=",
|
|
||||||
Equal => "=",
|
|
||||||
EqualEqual => "==",
|
|
||||||
Greater => ">",
|
|
||||||
GreaterEqual => ">=",
|
|
||||||
Less => "<",
|
|
||||||
LessEqual => "<=",
|
|
||||||
|
|
||||||
Identifier(v) => v,
|
|
||||||
String(v) => v,
|
|
||||||
Number(v) => v,
|
|
||||||
|
|
||||||
And => "and",
|
|
||||||
Async => "async",
|
|
||||||
Await => "await",
|
|
||||||
Class => "class",
|
|
||||||
Else => "else",
|
|
||||||
False => "false",
|
|
||||||
For => "for",
|
|
||||||
From => "from",
|
|
||||||
Fun => "fun",
|
|
||||||
If => "if",
|
|
||||||
Let => "let",
|
|
||||||
Or => "or",
|
|
||||||
Print => "print",
|
|
||||||
Return => "return",
|
|
||||||
Select => "select",
|
|
||||||
This => "this",
|
|
||||||
True => "true",
|
|
||||||
While => "while",
|
|
||||||
Yield => "yield",
|
|
||||||
|
|
||||||
Error(e) => e,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Into<String> for Token<'a> {
|
||||||
|
fn into(self) -> String {
|
||||||
|
self.as_str().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Tokens<'a> {
|
pub struct Tokens<'a> {
|
||||||
source: &'a str,
|
source: &'a str,
|
||||||
chars: std::str::CharIndices<'a>,
|
chars: std::str::CharIndices<'a>,
|
||||||
|
|
@ -140,8 +114,17 @@ impl<'a> Tokens<'a> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn token_position(&self, token: &Token) -> (usize, usize) {
|
/// Return the position of the given token as a (line, column) pair. By
|
||||||
let line_end_index = match self.newlines.binary_search(&token.start) {
|
/// convention, lines are 1-based and columns are 0-based. Also, in
|
||||||
|
/// keeping with the iterator-nature of the tokenizer, `None` here
|
||||||
|
/// indicates end-of-file, and will return the position of the end of the
|
||||||
|
/// file.
|
||||||
|
pub fn token_position(&self, token: &Option<Token>) -> (usize, usize) {
|
||||||
|
let start = match token {
|
||||||
|
Some(t) => t.start,
|
||||||
|
None => self.source.len(),
|
||||||
|
};
|
||||||
|
let line_end_index = match self.newlines.binary_search(&start) {
|
||||||
Ok(index) => index,
|
Ok(index) => index,
|
||||||
Err(index) => index,
|
Err(index) => index,
|
||||||
};
|
};
|
||||||
|
|
@ -151,15 +134,16 @@ impl<'a> Tokens<'a> {
|
||||||
self.newlines[line_end_index - 1] + 1
|
self.newlines[line_end_index - 1] + 1
|
||||||
};
|
};
|
||||||
let line_number = line_end_index + 1;
|
let line_number = line_end_index + 1;
|
||||||
let column_offset = token.start - line_start_pos;
|
let column_offset = start - line_start_pos;
|
||||||
(line_number, column_offset)
|
(line_number, column_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn token(&self, start: usize, kind: TokenKind<'a>) -> Token<'a> {
|
fn token(&self, start: usize, kind: TokenKind) -> Token<'a> {
|
||||||
Token::new(start, kind)
|
let value = &self.source[start..self.pos()];
|
||||||
|
Token::new(kind, start, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn number(&mut self, start: usize) -> TokenKind<'a> {
|
fn number(&mut self, start: usize) -> Token<'a> {
|
||||||
// First, the main part.
|
// First, the main part.
|
||||||
loop {
|
loop {
|
||||||
if !self.matches_digit() {
|
if !self.matches_digit() {
|
||||||
|
|
@ -198,9 +182,10 @@ impl<'a> Tokens<'a> {
|
||||||
if !saw_digit {
|
if !saw_digit {
|
||||||
// This is just a broken number.
|
// This is just a broken number.
|
||||||
let slice = &self.source[start..self.pos()];
|
let slice = &self.source[start..self.pos()];
|
||||||
return TokenKind::Error(format!(
|
return Token::error(
|
||||||
"Invalid floating-point literal: {slice}"
|
start,
|
||||||
));
|
format!("Invalid floating-point literal: {slice}"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -209,13 +194,13 @@ impl<'a> Tokens<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenKind::Number(&self.source[start..self.pos()])
|
self.token(start, TokenKind::Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string(&mut self, start: usize, delimiter: char) -> TokenKind<'a> {
|
fn string(&mut self, start: usize, delimiter: char) -> Token<'a> {
|
||||||
while !self.matches(delimiter) {
|
while !self.matches(delimiter) {
|
||||||
if self.eof() {
|
if self.eof() {
|
||||||
return TokenKind::Error("Unterminated string constant".to_string());
|
return Token::error(start, "Unterminated string constant".to_string());
|
||||||
}
|
}
|
||||||
if self.matches('\\') {
|
if self.matches('\\') {
|
||||||
self.advance();
|
self.advance();
|
||||||
|
|
@ -224,20 +209,12 @@ impl<'a> Tokens<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenKind::String(&self.source[start..self.pos()])
|
self.token(start, TokenKind::String)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identifier(&mut self, start: usize) -> TokenKind<'a> {
|
fn identifier_token_kind(ident: &str) -> TokenKind {
|
||||||
loop {
|
match ident.chars().nth(0).unwrap() {
|
||||||
// TODO: Use unicode identifier classes instead
|
'a' => {
|
||||||
if !self.matches_next(|c| c.is_ascii_alphanumeric() || c == '_') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ident = &self.source[start..self.pos()];
|
|
||||||
match ident.chars().nth(0) {
|
|
||||||
Some('a') => {
|
|
||||||
if ident == "and" {
|
if ident == "and" {
|
||||||
return TokenKind::And;
|
return TokenKind::And;
|
||||||
}
|
}
|
||||||
|
|
@ -248,17 +225,17 @@ impl<'a> Tokens<'a> {
|
||||||
return TokenKind::Await;
|
return TokenKind::Await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('c') => {
|
'c' => {
|
||||||
if ident == "class" {
|
if ident == "class" {
|
||||||
return TokenKind::Class;
|
return TokenKind::Class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('e') => {
|
'e' => {
|
||||||
if ident == "else" {
|
if ident == "else" {
|
||||||
return TokenKind::Else;
|
return TokenKind::Else;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('f') => {
|
'f' => {
|
||||||
if ident == "false" {
|
if ident == "false" {
|
||||||
return TokenKind::False;
|
return TokenKind::False;
|
||||||
}
|
}
|
||||||
|
|
@ -272,37 +249,37 @@ impl<'a> Tokens<'a> {
|
||||||
return TokenKind::Fun;
|
return TokenKind::Fun;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('i') => {
|
'i' => {
|
||||||
if ident == "if" {
|
if ident == "if" {
|
||||||
return TokenKind::If;
|
return TokenKind::If;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('l') => {
|
'l' => {
|
||||||
if ident == "let" {
|
if ident == "let" {
|
||||||
return TokenKind::Let;
|
return TokenKind::Let;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('o') => {
|
'o' => {
|
||||||
if ident == "or" {
|
if ident == "or" {
|
||||||
return TokenKind::Or;
|
return TokenKind::Or;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('p') => {
|
'p' => {
|
||||||
if ident == "print" {
|
if ident == "print" {
|
||||||
return TokenKind::Print;
|
return TokenKind::Print;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('r') => {
|
'r' => {
|
||||||
if ident == "return" {
|
if ident == "return" {
|
||||||
return TokenKind::Return;
|
return TokenKind::Return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('s') => {
|
's' => {
|
||||||
if ident == "select" {
|
if ident == "select" {
|
||||||
return TokenKind::Select;
|
return TokenKind::Select;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('t') => {
|
't' => {
|
||||||
if ident == "this" {
|
if ident == "this" {
|
||||||
return TokenKind::This;
|
return TokenKind::This;
|
||||||
}
|
}
|
||||||
|
|
@ -310,12 +287,12 @@ impl<'a> Tokens<'a> {
|
||||||
return TokenKind::True;
|
return TokenKind::True;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('w') => {
|
'w' => {
|
||||||
if ident == "while" {
|
if ident == "while" {
|
||||||
return TokenKind::While;
|
return TokenKind::While;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some('y') => {
|
'y' => {
|
||||||
if ident == "yield" {
|
if ident == "yield" {
|
||||||
return TokenKind::Yield;
|
return TokenKind::Yield;
|
||||||
}
|
}
|
||||||
|
|
@ -323,7 +300,20 @@ impl<'a> Tokens<'a> {
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenKind::Identifier(ident)
|
TokenKind::Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identifier(&mut self, start: usize) -> Token<'a> {
|
||||||
|
loop {
|
||||||
|
// TODO: Use unicode identifier classes instead
|
||||||
|
if !self.matches_next(|c| c.is_ascii_alphanumeric() || c == '_') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident = &self.source[start..self.pos()];
|
||||||
|
let kind = Self::identifier_token_kind(ident);
|
||||||
|
Token::new(kind, start, ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&mut self, ch: char) -> bool {
|
fn matches(&mut self, ch: char) -> bool {
|
||||||
|
|
@ -342,14 +332,9 @@ impl<'a> Tokens<'a> {
|
||||||
{
|
{
|
||||||
if let Some((_, next_ch)) = self.next_char {
|
if let Some((_, next_ch)) = self.next_char {
|
||||||
if f(next_ch) {
|
if f(next_ch) {
|
||||||
eprintln!("MATCHES NEXT: {next_ch}");
|
|
||||||
self.advance();
|
self.advance();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
eprintln!("NOT MATCHES NEXT: {next_ch}");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
eprintln!("E O F");
|
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +346,6 @@ impl<'a> Tokens<'a> {
|
||||||
fn advance(&mut self) -> Option<(usize, char)> {
|
fn advance(&mut self) -> Option<(usize, char)> {
|
||||||
let result = self.next_char;
|
let result = self.next_char;
|
||||||
self.next_char = self.chars.next();
|
self.next_char = self.chars.next();
|
||||||
eprintln!("NEXT: {:?}", self.next_char);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,57 +383,57 @@ impl<'a> std::iter::Iterator for Tokens<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = match c {
|
let token = match c {
|
||||||
'{' => TokenKind::LeftBrace,
|
'{' => self.token(pos, TokenKind::LeftBrace),
|
||||||
'}' => TokenKind::RightBrace,
|
'}' => self.token(pos, TokenKind::RightBrace),
|
||||||
'[' => TokenKind::LeftBracket,
|
'[' => self.token(pos, TokenKind::LeftBracket),
|
||||||
']' => TokenKind::RightBracket,
|
']' => self.token(pos, TokenKind::RightBracket),
|
||||||
'(' => TokenKind::LeftParen,
|
'(' => self.token(pos, TokenKind::LeftParen),
|
||||||
')' => TokenKind::RightParen,
|
')' => self.token(pos, TokenKind::RightParen),
|
||||||
',' => TokenKind::Comma,
|
',' => self.token(pos, TokenKind::Comma),
|
||||||
'.' => TokenKind::Dot,
|
'.' => self.token(pos, TokenKind::Dot),
|
||||||
'-' => {
|
'-' => {
|
||||||
if self.matches_next(|c| c.is_ascii_digit()) {
|
if self.matches_next(|c| c.is_ascii_digit()) {
|
||||||
self.number(pos)
|
self.number(pos)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Minus
|
self.token(pos, TokenKind::Minus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'+' => {
|
'+' => {
|
||||||
if self.matches_next(|c| c.is_ascii_digit()) {
|
if self.matches_next(|c| c.is_ascii_digit()) {
|
||||||
self.number(pos)
|
self.number(pos)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Plus
|
self.token(pos, TokenKind::Plus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
';' => TokenKind::Semicolon,
|
';' => self.token(pos, TokenKind::Semicolon),
|
||||||
'/' => TokenKind::Slash,
|
'/' => self.token(pos, TokenKind::Slash),
|
||||||
'*' => TokenKind::Star,
|
'*' => self.token(pos, TokenKind::Star),
|
||||||
'!' => {
|
'!' => {
|
||||||
if self.matches('=') {
|
if self.matches('=') {
|
||||||
TokenKind::BangEqual
|
self.token(pos, TokenKind::BangEqual)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Bang
|
self.token(pos, TokenKind::Bang)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'=' => {
|
'=' => {
|
||||||
if self.matches('=') {
|
if self.matches('=') {
|
||||||
TokenKind::EqualEqual
|
self.token(pos, TokenKind::EqualEqual)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Equal
|
self.token(pos, TokenKind::Equal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'>' => {
|
'>' => {
|
||||||
if self.matches('=') {
|
if self.matches('=') {
|
||||||
TokenKind::GreaterEqual
|
self.token(pos, TokenKind::GreaterEqual)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Greater
|
self.token(pos, TokenKind::Greater)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' => {
|
'<' => {
|
||||||
if self.matches('=') {
|
if self.matches('=') {
|
||||||
TokenKind::LessEqual
|
self.token(pos, TokenKind::LessEqual)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Less
|
self.token(pos, TokenKind::Less)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => self.string(pos, '\''),
|
'\'' => self.string(pos, '\''),
|
||||||
|
|
@ -460,11 +444,10 @@ impl<'a> std::iter::Iterator for Tokens<'a> {
|
||||||
} else if c.is_ascii_alphabetic() || c == '_' {
|
} else if c.is_ascii_alphabetic() || c == '_' {
|
||||||
self.identifier(pos)
|
self.identifier(pos)
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Error(format!("Unexpected character '{c}'"))
|
Token::error(pos, format!("Unexpected character '{c}'"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let token = self.token(pos, token);
|
|
||||||
Some(token)
|
Some(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -480,7 +463,12 @@ mod tests {
|
||||||
fn $name() {
|
fn $name() {
|
||||||
use TokenKind::*;
|
use TokenKind::*;
|
||||||
let tokens: Vec<_> = Tokens::new($input).collect();
|
let tokens: Vec<_> = Tokens::new($input).collect();
|
||||||
let expected = vec![$($s),*];
|
|
||||||
|
let expected: Vec<Token> = (vec![$($s),*])
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| Token::new(t.1, t.0, t.2))
|
||||||
|
.collect();
|
||||||
|
|
||||||
assert_eq!(expected, tokens);
|
assert_eq!(expected, tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -489,81 +477,81 @@ mod tests {
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
numbers,
|
numbers,
|
||||||
"1 1.0 1.2e7 2.3e+7 3.3E-06 7_6 8.0e_8",
|
"1 1.0 1.2e7 2.3e+7 3.3E-06 7_6 8.0e_8",
|
||||||
Token::new(0, Number("1")),
|
(0, Number, "1"),
|
||||||
Token::new(2, Number("1.0")),
|
(2, Number, "1.0"),
|
||||||
Token::new(6, Number("1.2e7")),
|
(6, Number, "1.2e7"),
|
||||||
Token::new(12, Number("2.3e+7")),
|
(12, Number, "2.3e+7"),
|
||||||
Token::new(19, Number("3.3E-06")),
|
(19, Number, "3.3E-06"),
|
||||||
Token::new(27, Number("7_6")),
|
(27, Number, "7_6"),
|
||||||
Token::new(31, Number("8.0e_8"))
|
(31, Number, "8.0e_8")
|
||||||
);
|
);
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
identifiers,
|
identifiers,
|
||||||
"asdf x _123 a_23 x3a and or yield async await class else false for from",
|
"asdf x _123 a_23 x3a and or yield async await class else false for from",
|
||||||
Token::new(0, Identifier("asdf")),
|
(0, Identifier, "asdf"),
|
||||||
Token::new(5, Identifier("x")),
|
(5, Identifier, "x"),
|
||||||
Token::new(7, Identifier("_123")),
|
(7, Identifier, "_123"),
|
||||||
Token::new(12, Identifier("a_23")),
|
(12, Identifier, "a_23"),
|
||||||
Token::new(17, Identifier("x3a")),
|
(17, Identifier, "x3a"),
|
||||||
Token::new(21, And),
|
(21, And, "and"),
|
||||||
Token::new(25, Or),
|
(25, Or, "or"),
|
||||||
Token::new(28, Yield),
|
(28, Yield, "yield"),
|
||||||
Token::new(34, Async),
|
(34, Async, "async"),
|
||||||
Token::new(40, Await),
|
(40, Await, "await"),
|
||||||
Token::new(46, Class),
|
(46, Class, "class"),
|
||||||
Token::new(52, Else),
|
(52, Else, "else"),
|
||||||
Token::new(57, False),
|
(57, False, "false"),
|
||||||
Token::new(63, For),
|
(63, For, "for"),
|
||||||
Token::new(67, From)
|
(67, From, "from")
|
||||||
);
|
);
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
more_keywords,
|
more_keywords,
|
||||||
"fun if let print return select this true while truewhile",
|
"fun if let print return select this true while truewhile",
|
||||||
Token::new(0, Fun),
|
(0, Fun, "fun"),
|
||||||
Token::new(4, If),
|
(4, If, "if"),
|
||||||
Token::new(7, Let),
|
(7, Let, "let"),
|
||||||
Token::new(11, Print),
|
(11, Print, "print"),
|
||||||
Token::new(17, Return),
|
(17, Return, "return"),
|
||||||
Token::new(24, Select),
|
(24, Select, "select"),
|
||||||
Token::new(31, This),
|
(31, This, "this"),
|
||||||
Token::new(36, True),
|
(36, True, "true"),
|
||||||
Token::new(41, While),
|
(41, While, "while"),
|
||||||
Token::new(47, Identifier("truewhile"))
|
(47, Identifier, "truewhile")
|
||||||
);
|
);
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
strings,
|
strings,
|
||||||
r#"'this is a string that\'s great!\r\n' "foo's" 'bar"s' "#,
|
r#"'this is a string that\'s great!\r\n' "foo's" 'bar"s' "#,
|
||||||
Token::new(0, String(r#"'this is a string that\'s great!\r\n'"#)),
|
(0, String, r#"'this is a string that\'s great!\r\n'"#),
|
||||||
Token::new(38, String(r#""foo's""#)),
|
(38, String, r#""foo's""#),
|
||||||
Token::new(46, String("'bar\"s'"))
|
(46, String, "'bar\"s'")
|
||||||
);
|
);
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
symbols,
|
symbols,
|
||||||
"{ } ( ) [ ] . ! != < <= > >= = == , - + * / ;",
|
"{ } ( ) [ ] . ! != < <= > >= = == , - + * / ;",
|
||||||
Token::new(0, LeftBrace),
|
(0, LeftBrace, "{"),
|
||||||
Token::new(2, RightBrace),
|
(2, RightBrace, "}"),
|
||||||
Token::new(4, LeftParen),
|
(4, LeftParen, "("),
|
||||||
Token::new(6, RightParen),
|
(6, RightParen, ")"),
|
||||||
Token::new(8, LeftBracket),
|
(8, LeftBracket, "["),
|
||||||
Token::new(10, RightBracket),
|
(10, RightBracket, "]"),
|
||||||
Token::new(12, Dot),
|
(12, Dot, "."),
|
||||||
Token::new(14, Bang),
|
(14, Bang, "!"),
|
||||||
Token::new(16, BangEqual),
|
(16, BangEqual, "!="),
|
||||||
Token::new(19, Less),
|
(19, Less, "<"),
|
||||||
Token::new(21, LessEqual),
|
(21, LessEqual, "<="),
|
||||||
Token::new(24, Greater),
|
(24, Greater, ">"),
|
||||||
Token::new(26, GreaterEqual),
|
(26, GreaterEqual, ">="),
|
||||||
Token::new(29, Equal),
|
(29, Equal, "="),
|
||||||
Token::new(31, EqualEqual),
|
(31, EqualEqual, "=="),
|
||||||
Token::new(34, Comma),
|
(34, Comma, ","),
|
||||||
Token::new(36, Minus),
|
(36, Minus, "-"),
|
||||||
Token::new(38, Plus),
|
(38, Plus, "+"),
|
||||||
Token::new(40, Star),
|
(40, Star, "*"),
|
||||||
Token::new(42, Slash),
|
(42, Slash, "/"),
|
||||||
Token::new(44, Semicolon)
|
(44, Semicolon, ";")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue