Привет читающим!
Эта статья будет значительно короче двух предыдущих, и будущие статьи будут такие же короткие, потому что так легче писать и искать ошибки. Также, думаю, читать так будет удобнее.
Как будем писать?
Я много как пытался писать это раньше, и нашел наиболее удобный для себя способ.
У Stmt будет публичный статический метод define(&Parser). Он будет смотреть токены и пытаться понял, какой стейтмент находится на текущей позиции. Для этого мы сделаем методы парсера peek и error публичными, чтобы мы могли использовать извне. Этот метод будет возвращать один из вариантов нового перечисления — StmtKind. В парсере будет метод stmt, он будет проверять, что вернул define и вызывать соответствующий метод для парсинга этого стейтмента.
Начинаем писать!
Сразу сделаем peek и error публичными:
pub fn error(&self, msg: &str, token: Token) -> String { ... }pub fn peek(&self, offset: i8) -> Token { ... }
В пакете parser создаем новый файл — ast.rs.
parser/ast.rs
use super::Parser;use super::expr::Expr;use crate::lexer::token::TKind;#[derive(Debug)]pub enum Stmt { Assign(String, AssignOp, Expr), Print(Vec<Expr>),}#[derive(Debug)]pub enum StmtKind { Assign, Print,}#[derive(Debug, Default)]pub enum AssignOp { #[default] Assign,}
Возможно, вы подумаете зачем я создал перечисление AssignOp? Все просто. На данный момент у нас нет токенов для других вариантов присвоения типа +=, только =, и пока что тут будет 1 стандартный вариант, другие, возможно, добавим позже.
Поясню за параметры вариантов Stmt:
Assign(String, AssignOp, Expr) — String тут выступает в качестве идентификатора переменной. AssignOp соответственно тип присвоения. Expr значение, которое мы будем присваивать.
Print(Vec<Expr>) — вектор выражений тут собственно просто все то, что мы будем выводить.
Метод define
impl Stmt { pub fn define(pr: &Parser) -> StmtKind { match (pr.peek(0).kind, pr.peek(1).kind) { (TKind::Id(_), TKind::Assign) => StmtKind::Assign, (TKind::Print, _) => StmtKind::Print, _ => panic!("{}", pr.error("Unknown statement", pr.peek(0))), } }}
Тут тоже все довольно просто. Проверяем первые 2 типа токена с текущей позиции, и смотрим, что это за токены. Если это идентификатор и знак равно (Мы в будущем, как я писал ранее, добавим другие варианты), то это присвоение, если это кейворд print, то это соответственно Print. В случае, если мы не нашли подходящий паттерн, то паникуем с сообщением о неизвестном стейтменте.
Print стейтмент
Он посложнее, чем Assign, так что начнем с него. Сразу напишем удобный метод для парсинга передаваемых аргументов
parser/parser.rs: Parser
fn parse_args(&mut self) -> Vec<Expr> { let mut args = Vec::new(); while self.peek(0).kind != TKind::Eof { let value = self.expr(); args.push(value); let current = self.peek(0); if current.kind == TKind::RParen { break; } if !self.check(TKind::Comma) { panic!( "{}", self.error( &format!("Expected ')' or ',', found {:?}", current), current ) ); } } args}
В бесконечном цикле, пока текущий токен не равен EOF: парсим выражение и сразу добавляем в список аргументов. Проверяем следующий токен, если это запятая, то просто продолжаем цикл, в случае если это закрывающая скобка, то завершаем цикл. Если это не запятая и не закрывающая скобка, то выдаем ошибку о неожиданном токене.
fn parse_print(&mut self) -> Stmt { self.advance(1); if !self.check(TKind::LParen) { let current = self.peek(0); panic!( "{}", self.error(&format!("Expected '(', found {:?}", current), current) ); } let args = self.parse_args(); if !self.check(TKind::RParen) { let current = self.peek(0); panic!( "{}", self.error(&format!("Expected ')', found {:?}", current), current) ); } Stmt::Print(args)}
Assign стейтмент
fn parse_assign(&mut self) -> Stmt { let id = match self.peek(0).kind { TKind::Id(id) => { self.advance(1); id } _ => unreachable!("{:?}", self.peek(0).kind), }; let assign = AssignOp::default(); self.advance(1); let value = self.expr(); Stmt::Assign(id, assign, value)}
Сначала получаем идентификатор. Он не может быть не идентификатором, так как define это уже проверил, так что ставим unreachable для других вариантов.
Определяем, что идет после идентификатора. У нас это может быть только знак равно, так что в других случаях паникуем. Далее пропускаем знак присвоения и парсим значение для присвоения. В конце возвращаем соответствующий стейтмент.
Парсинг
fn stmt(&mut self) -> Stmt { match Stmt::define(self) { StmtKind::Assign => self.parse_assign(), StmtKind::Print => self.parse_print(), }}
Этот метод мы будем вызывать для парсинга стейтментов, он определяет тип и вызывает соответствующие методы.
Создаем метод для парсинга списка стейтменов:
pub fn parse(&mut self) -> Vec<Stmt> { let mut stmts = vec![]; while self.peek(0).kind != TKind::Eof { let expr = self.stmt(); stmts.push(expr); } stmts}
Пока текущий токен не EOF парсим стейтменты и добавляем в список.
Добавим новый тест в tests/parsert.rs
fn statement() { let source = "a = 4print(\"a is \", a)" .trim(); let tokens = Lexer::new(source).tokenize(); println!("Source: {}", source); let stmts = Parser::new(tokens, source).parse(); println!("Statements:"); for stmt in stmts { println!("{:?}", stmt); }}
Source: a = 4print("a is ", a)Statements:Assign("a", Assign, Num(4.0, Info { line: 0, offset: 4, len: 1 }))Print([Str("a is ", Info { line: 1, offset: 6, len: 7 }), Id("a", Info { line: 1, offset: 15, len: 3 })])
Итог
Теперь парсер уже может парсить первые стейтменты! В следующей статье мы напишем парсинг для if-elif-else.
ссылка на оригинал статьи https://habr.com/ru/articles/1044772/