Создание своего языка программирования на Rust #3: Парсинг стейтментов вывода и присвоения

от автора

Привет читающим!

Эта статья будет значительно короче двух предыдущих, и будущие статьи будут такие же короткие, потому что так легче писать и искать ошибки. Также, думаю, читать так будет удобнее.

Как будем писать?

Я много как пытался писать это раньше, и нашел наиболее удобный для себя способ.

У 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/