flowsurface-indicator-engine 0.9.0

Aggr-level live scripting engine for Flowsurface indicator sources
Documentation
mod expressions;
mod statements;

use super::ast::constant_value;
use super::ast::{AstArgument, AstCall, AstEnum, AstFunction, AstParameter, AstProgram};
use super::lexer::{Token, TokenKind};
use crate::diagnostics::{Diagnostic, Span};
use crate::hir::BinaryOperator;
use crate::types::Value;
use std::path::PathBuf;

pub(super) const MAX_PARSER_NESTING_DEPTH: usize = 256;

pub(super) struct Parser {
    pub(super) tokens: Vec<Token>,
    pub(super) path: PathBuf,
    pub(super) index: usize,
    pub(super) diagnostics: Vec<Diagnostic>,
    pub(super) nesting_limit_reported: bool,
}

pub(super) struct ParseOutput {
    pub(super) ast: AstProgram,
    pub(super) diagnostics: Vec<Diagnostic>,
}

pub(super) fn parse(tokens: Vec<Token>, path: PathBuf) -> ParseOutput {
    let mut parser = Parser::new(tokens, path);
    let ast = parser.parse_program();
    ParseOutput {
        ast,
        diagnostics: parser.diagnostics,
    }
}

impl Parser {
    pub(super) fn new(tokens: Vec<Token>, path: PathBuf) -> Self {
        Self {
            tokens,
            path,
            index: 0,
            diagnostics: Vec::new(),
            nesting_limit_reported: false,
        }
    }

    pub(super) fn parse_program(&mut self) -> AstProgram {
        self.skip_boundaries();
        self.parse_language_directive();
        let indicator = if self.is_named("indicator") {
            let value = self.parse_call();
            self.skip_boundaries();
            value
        } else {
            None
        };
        let mut functions = Vec::new();
        let mut enums = Vec::new();
        let mut statements = Vec::new();
        while !self.at_end() {
            if self.consume_keyword("enum") {
                if let Some(item) = self.parse_enum() {
                    enums.push(item);
                } else {
                    self.synchronize_statement();
                }
                self.skip_boundaries();
                continue;
            }
            if self.consume_keyword("fn") {
                if let Some(function) = self.parse_function() {
                    functions.push(function);
                } else {
                    self.synchronize_statement();
                }
                self.skip_boundaries();
                continue;
            }
            if let Some(statement) = self.parse_statement() {
                statements.push(statement);
            } else {
                self.synchronize_statement();
            }
            self.skip_boundaries();
        }
        AstProgram {
            indicator,
            functions,
            enums,
            statements,
        }
    }

    pub(super) fn parse_language_directive(&mut self) {
        if !self.is_named("language") {
            self.error(self.current().span, "source must begin with language(1)");
            return;
        }
        let Some(call) = self.parse_call() else {
            self.error(self.current().span, "source must begin with language(1)");
            return;
        };
        self.skip_boundaries();

        let valid_name = call.path.len() == 1 && call.path[0] == "language";
        let valid_arguments = call.arguments.len() == 1
            && call.arguments[0].name.is_none()
            && matches!(
                constant_value(&call.arguments[0].expression),
                Some(Value::Int(1))
            );
        if !valid_name || !valid_arguments {
            self.error(call.span, "source must begin with language(1)");
        }
    }

    pub(super) fn parse_enum(&mut self) -> Option<AstEnum> {
        let start = self.previous_span().start;
        let TokenKind::Identifier(name) = self.current().kind.clone() else {
            self.error(self.current().span, "expected an enum name");
            return None;
        };
        self.index += 1;
        if !self.consume_kind(TokenKind::LeftBrace) {
            self.error(
                self.current().span,
                "expected an opening brace after enum name",
            );
            return None;
        }
        let mut variants = Vec::new();
        self.skip_boundaries();
        while !self.check(&TokenKind::RightBrace) && !self.at_end() {
            let TokenKind::Identifier(variant) = self.current().kind.clone() else {
                self.error(self.current().span, "expected an enum variant name");
                return None;
            };
            self.index += 1;
            variants.push(variant);
            self.skip_boundaries();
            if !self.consume_kind(TokenKind::Comma) {
                self.skip_boundaries();
                if self.check(&TokenKind::RightBrace) {
                    break;
                }
                if matches!(self.current().kind, TokenKind::Identifier(_)) {
                    continue;
                }
                self.error(
                    self.current().span,
                    "expected a comma between enum variants",
                );
                return None;
            }
            self.skip_boundaries();
        }
        if !self.consume_kind(TokenKind::RightBrace) {
            self.error(
                self.current().span,
                "expected a closing brace after enum variants",
            );
            return None;
        }
        Some(AstEnum {
            name,
            variants,
            span: Span::new(start, self.previous_span().end),
        })
    }

    pub(super) fn parse_function(&mut self) -> Option<AstFunction> {
        let start = self.previous_span().start;
        let TokenKind::Identifier(name) = self.current().kind.clone() else {
            self.error(self.current().span, "expected a function name");
            return None;
        };
        self.index += 1;
        if !self.consume_kind(TokenKind::LeftParen) {
            self.error(
                self.current().span,
                "expected an opening parenthesis after function name",
            );
            return None;
        }
        let mut parameters = Vec::new();
        self.skip_inline_boundaries();
        while !self.check(&TokenKind::RightParen) && !self.at_end() {
            let TokenKind::Identifier(parameter) = self.current().kind.clone() else {
                self.error(self.current().span, "expected a function parameter name");
                return None;
            };
            self.index += 1;
            self.skip_inline_boundaries();
            let default = if self.consume_kind(TokenKind::Equal) {
                Some(self.parse_expression(0)?)
            } else {
                None
            };
            parameters.push(AstParameter {
                name: parameter,
                default,
            });
            self.skip_inline_boundaries();
            if !self.consume_kind(TokenKind::Comma) {
                break;
            }
            self.skip_inline_boundaries();
        }
        if !self.consume_kind(TokenKind::RightParen) {
            self.error(
                self.current().span,
                "expected a closing parenthesis after parameters",
            );
            return None;
        }
        let body = self.parse_block_or_statement()?;
        Some(AstFunction {
            name,
            parameters,
            body,
            span: Span::new(start, self.previous_span().end),
        })
    }

    pub(super) fn parse_call(&mut self) -> Option<AstCall> {
        let start = self.current().span.start;
        let path = self.parse_path()?;
        if !self.consume_kind(TokenKind::LeftParen) {
            self.error(
                self.current().span,
                "expected an opening parenthesis after call name",
            );
            return None;
        }
        let arguments = self.parse_call_arguments()?;
        Some(AstCall {
            path,
            arguments,
            span: Span::new(start, self.previous_span().end),
        })
    }

    pub(super) fn parse_call_arguments(&mut self) -> Option<Vec<AstArgument>> {
        let mut arguments = Vec::new();
        self.skip_inline_boundaries();
        while !self.check(&TokenKind::RightParen) && !self.at_end() {
            let named = match (self.current().kind.clone(), self.peek_kind(1)) {
                (TokenKind::Identifier(name), Some(TokenKind::Equal)) => {
                    self.index += 2;
                    Some(name)
                }
                _ => None,
            };
            let expression = self.parse_expression(0)?;
            arguments.push(AstArgument {
                name: named,
                expression,
            });
            self.skip_inline_boundaries();
            if !self.consume_kind(TokenKind::Comma) {
                break;
            }
            self.skip_inline_boundaries();
        }
        if !self.consume_kind(TokenKind::RightParen) {
            self.error(
                self.current().span,
                "expected a closing parenthesis after arguments",
            );
            return None;
        }
        Some(arguments)
    }

    pub(super) fn parse_path(&mut self) -> Option<Vec<String>> {
        let TokenKind::Identifier(first) = self.current().kind.clone() else {
            self.error(self.current().span, "expected a name");
            return None;
        };
        self.index += 1;
        let mut path = vec![first];
        while self.consume_kind(TokenKind::Dot) {
            let TokenKind::Identifier(segment) = self.current().kind.clone() else {
                self.error(self.current().span, "expected a name after a dot");
                return None;
            };
            path.push(segment);
            self.index += 1;
        }
        Some(path)
    }

    pub(super) fn binary_operator(&self) -> Option<(BinaryOperator, u8)> {
        Some(match self.current().kind {
            TokenKind::OrOr => (BinaryOperator::Or, 1),
            TokenKind::AndAnd => (BinaryOperator::And, 2),
            TokenKind::EqualEqual => (BinaryOperator::Equal, 3),
            TokenKind::BangEqual => (BinaryOperator::NotEqual, 3),
            TokenKind::Less => (BinaryOperator::Less, 4),
            TokenKind::LessEqual => (BinaryOperator::LessEqual, 4),
            TokenKind::Greater => (BinaryOperator::Greater, 4),
            TokenKind::GreaterEqual => (BinaryOperator::GreaterEqual, 4),
            TokenKind::Plus => (BinaryOperator::Add, 5),
            TokenKind::Minus => (BinaryOperator::Subtract, 5),
            TokenKind::Star => (BinaryOperator::Multiply, 6),
            TokenKind::Slash => (BinaryOperator::Divide, 6),
            TokenKind::Percent => (BinaryOperator::Remainder, 6),
            _ => return None,
        })
    }

    pub(super) fn synchronize_statement(&mut self) {
        while !self.at_end()
            && !matches!(
                self.current().kind,
                TokenKind::Newline | TokenKind::Semicolon
            )
        {
            self.index += 1;
        }
        self.skip_boundaries();
    }

    pub(super) fn skip_boundaries(&mut self) {
        while matches!(
            self.current().kind,
            TokenKind::Newline | TokenKind::Semicolon
        ) {
            self.index += 1;
        }
    }

    pub(super) fn skip_inline_boundaries(&mut self) {
        while matches!(self.current().kind, TokenKind::Newline) {
            self.index += 1;
        }
    }

    pub(super) fn is_named(&self, name: &str) -> bool {
        matches!(&self.current().kind, TokenKind::Identifier(value) if value == name)
    }

    pub(super) fn consume_keyword(&mut self, keyword: &str) -> bool {
        if self.is_named(keyword) {
            self.index += 1;
            true
        } else {
            false
        }
    }

    pub(super) fn consume_kind(&mut self, kind: TokenKind) -> bool {
        if self.current().kind == kind {
            self.index += 1;
            true
        } else {
            false
        }
    }

    pub(super) fn check(&self, kind: &TokenKind) -> bool {
        &self.current().kind == kind
    }

    pub(super) fn peek_kind(&self, offset: usize) -> Option<&TokenKind> {
        self.tokens
            .get(self.index + offset)
            .map(|token| &token.kind)
    }

    pub(super) fn previous_span(&self) -> Span {
        self.tokens
            .get(self.index.saturating_sub(1))
            .map_or(self.current().span, |token| token.span)
    }

    pub(super) fn current(&self) -> &Token {
        &self.tokens[self.index.min(self.tokens.len() - 1)]
    }

    pub(super) fn at_end(&self) -> bool {
        matches!(self.current().kind, TokenKind::End)
    }

    pub(super) fn report_nesting_limit(&mut self) {
        if !self.nesting_limit_reported {
            self.error(
                self.current().span,
                format!("source exceeds parser nesting limit of {MAX_PARSER_NESTING_DEPTH}"),
            );
            self.nesting_limit_reported = true;
        }
        self.index = self.tokens.len().saturating_sub(1);
    }

    pub(super) fn error(&mut self, span: Span, message: impl Into<String>) {
        self.diagnostics
            .push(Diagnostic::error(self.path.clone(), span, message));
    }
}