From 9f29ea357e0ac21609375da165ec340e729f7e3a Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sat, 1 Aug 2026 17:26:40 +0800 Subject: [PATCH 1/2] refactor(parser): give Syntax and Grammar errors structured payloads Replace the unstructured String payloads of Error::Syntax and Error::Grammar with structured line/column/message fields, matching the shape Error::InvalidNumber already had. The pest error mapping now extracts the start position from pest's LineColLocation, and grammar_error moves the line/column text out of the message string into the variant fields while keeping the compact source snippet inside the displayed text. grammar_error_static uses a 0/0 position sentinel and the Syntax display still renders pest's message verbatim, so all user-visible error output is unchanged. Also replace the remaining panic islands in the parser with grammar errors: the two .expect() calls in parse_type_spec for malformed reference types, the unchecked inner_pairs[0] indexing in parse_var_def, and the unchecked inner_pairs[i + 1] operand access in the four left-associative expression loops (bool_expr, bool_and_term, arith_expr, arith_term) now return Error::Grammar instead of panicking. Finally, add //! module docs to the common, decl, expr, and stmt submodules, aligning with the submodule descriptions in the parser module header. --- src/parser.rs | 14 +++++++++++- src/parser/common.rs | 52 ++++++++++++++++++++++++++++++++++---------- src/parser/decl.rs | 23 ++++++++++++++++---- src/parser/expr.rs | 37 +++++++++++++++++++++++++++---- src/parser/stmt.rs | 8 +++++++ 5 files changed, 113 insertions(+), 21 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 1e15a9f..96eebeb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -110,7 +110,19 @@ impl<'a> ParseContext<'a> { fn parse(&self) -> ParseResult> { // Run the pest parser; convert any pest::Error into Error::Syntax. let pairs = >::parse(Rule::program, self.input) - .map_err(|e| Error::Syntax(e.to_string()))?; + .map_err(|e| { + // Pest reports either a single position or a span start/end; + // use the start position for the structured fields. + let (line, column) = match e.line_col { + pest::error::LineColLocation::Pos(pos) => pos, + pest::error::LineColLocation::Span(start, _) => start, + }; + Error::Syntax { + line, + column, + message: e.to_string(), + } + })?; let mut use_stmts = Vec::new(); let mut elements = Vec::new(); diff --git a/src/parser/common.rs b/src/parser/common.rs index 4b9ff66..6110f95 100644 --- a/src/parser/common.rs +++ b/src/parser/common.rs @@ -1,12 +1,27 @@ +//! Shared definitions and helpers for the TeaLang parser. +//! +//! Everything in this submodule is `pub(crate)` infrastructure used by the +//! other parser submodules: the [`Error`] type and [`ParseResult`] alias, the +//! pest-derived [`TeaLangParser`] (generated from `tealang.pest`) together +//! with the [`Pair`] parse-tree node alias, and helper functions for building +//! [`Error::Grammar`] values with source positions, collapsing source +//! snippets into compact previews, and parsing integer literals. + use pest_derive::Parser as DeriveParser; /// Errors that can be produced during parsing of a TeaLang source file. #[derive(Debug, thiserror::Error)] pub enum Error { /// A syntax error reported directly by the PEG parser (pest). - /// The inner `String` contains the human-readable pest error message. - #[error("{0}")] - Syntax(String), + /// `line` and `column` give the 1-based position where the error was + /// detected; `message` contains the human-readable pest error text, + /// which itself renders the offending source line and position. + #[error("{message}")] + Syntax { + line: usize, + column: usize, + message: String, + }, /// An integer literal that could not be parsed as a valid `i32`. /// Includes the original source text, its position, and the underlying @@ -25,10 +40,16 @@ pub enum Error { Io(#[from] std::io::Error), /// The parse tree had an unexpected structure at the given location. - /// The inner `String` names the grammar rule or context where the - /// unexpected structure was found. - #[error("unexpected parse tree structure in {0}")] - Grammar(String), + /// `message` names the grammar rule or context where the unexpected + /// structure was found, followed by a compact source snippet. A + /// `line`/`column` of `0` means that no position was available + /// (see [`grammar_error_static`]). + #[error("unexpected parse tree structure in {message} at line {line}, column {column}")] + Grammar { + line: usize, + column: usize, + message: String, + }, } /// The pest-derived parser for the TeaLang grammar. @@ -91,18 +112,25 @@ pub(crate) fn grammar_error(context: &'static str, pair: &Pair<'_>) -> Error { let (line, column) = span.start_pos().line_col(); let near = compact_snippet(span.as_str()); - Error::Grammar(format!( - "{context} at line {line}, column {column}, near `{near}`" - )) + Error::Grammar { + line, + column, + message: format!("{context} near `{near}`"), + } } /// Creates a [`Error::Grammar`] variant from a static string alone, without /// access to a specific parse-tree node. /// /// Use this when position information is unavailable (e.g., when validating -/// program state rather than a particular source span). +/// program state rather than a particular source span). The `line` and +/// `column` fields are set to `0` as a "position unavailable" sentinel. pub(crate) fn grammar_error_static(context: &'static str) -> Error { - Error::Grammar(context.to_string()) + Error::Grammar { + line: 0, + column: 0, + message: context.to_string(), + } } /// Returns the byte offset of the start of `pair`'s span within the source diff --git a/src/parser/decl.rs b/src/parser/decl.rs index 959045f..6bb9582 100644 --- a/src/parser/decl.rs +++ b/src/parser/decl.rs @@ -1,3 +1,11 @@ +//! Parsing of TeaLang declaration and definition rules. +//! +//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! that lower declaration-oriented parse-tree nodes into the corresponding AST +//! types: `use` statements, program elements, struct definitions, typed +//! variable declarations and definitions (including array initialisers), and +//! function declarations and definitions. + use crate::ast; use super::common::{get_pos, grammar_error, parse_num, Pair, ParseResult, Rule}; @@ -163,11 +171,14 @@ impl<'a> ParseContext<'a> { /// /// Recognises reference types (`&T`), the built-in `i32` keyword, and /// user-defined composite (struct) types by their identifier. Returns - /// `Ok(None)` when the node is empty or contains no recognised type rule. + /// `Ok(None)` when the node is empty or contains no recognised type rule, + /// and [`Error::Grammar`] if a reference type is missing or has an empty + /// inner type specifier. /// /// # Arguments /// * `pair` – the `type_spec` parse-tree node. pub(crate) fn parse_type_spec(&self, pair: Pair) -> ParseResult> { + let pair_for_error = pair.clone(); // Record the start position for use in the returned AST node. let pos = get_pos(&pair); @@ -181,10 +192,10 @@ impl<'a> ParseContext<'a> { let inner_type_spec = ref_children .iter() .find(|c| c.as_rule() == Rule::type_spec) - .expect("Ref type_spec must have inner type_spec"); + .ok_or_else(|| grammar_error("ref_type.type_spec", &pair_for_error))?; let inner_ts = self .parse_type_spec(inner_type_spec.clone())? - .expect("Ref inner type_spec must not be empty"); + .ok_or_else(|| grammar_error("ref_type.empty", &pair_for_error))?; return Ok(Some(ast::TypeSpecifier { pos, inner: ast::TypeSpecifierInner::Reference(Box::new(inner_ts)), @@ -255,7 +266,11 @@ impl<'a> ParseContext<'a> { let inner_pairs: Vec<_> = pair.into_inner().collect(); // The first child is always the variable name identifier. - let identifier = inner_pairs[0].as_str().to_string(); + let identifier = inner_pairs + .first() + .ok_or_else(|| grammar_error("var_def.identifier", &pair_for_error))? + .as_str() + .to_string(); // Determine the form of the definition by looking for key child rules. let has_initializer = inner_pairs diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 1a6d94b..1761393 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -1,3 +1,12 @@ +//! Parsing of TeaLang expression rules. +//! +//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! that lower expression-oriented parse-tree nodes into the corresponding AST +//! types: right-hand values, Boolean expressions (`||`, `&&`, `!`, and +//! comparisons), arithmetic expressions (the `+`/`-` and `*`/`/` precedence +//! layers), and expression units (literals, parenthesised expressions, +//! function calls, references, and left-value access chains). + use crate::ast; use super::common::{get_pos, grammar_error, parse_num, Pair, ParseResult, Rule}; @@ -74,7 +83,12 @@ impl<'a> ParseContext<'a> { while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::op_or { // Consume the operator and the next operand together. - let right = self.parse_bool_and_term(inner_pairs[i + 1].clone())?; + let right = self.parse_bool_and_term( + inner_pairs + .get(i + 1) + .ok_or_else(|| grammar_error("bool_expr.operand", &pair_for_error))? + .clone(), + )?; expr = Box::new(ast::BoolExpr { pos: expr.pos, inner: ast::BoolExprInner::BoolBiOpExpr(Box::new(ast::BoolBiOpExpr { @@ -119,7 +133,12 @@ impl<'a> ParseContext<'a> { let mut i = 1; while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::op_and { - let right_unit = self.parse_bool_unit_atom(inner_pairs[i + 1].clone())?; + let right_unit = self.parse_bool_unit_atom( + inner_pairs + .get(i + 1) + .ok_or_else(|| grammar_error("bool_and_term.operand", &pair_for_error))? + .clone(), + )?; let right_expr = Box::new(ast::BoolExpr { pos: right_unit.pos, inner: ast::BoolExprInner::BoolUnit(right_unit), @@ -333,7 +352,12 @@ impl<'a> ParseContext<'a> { while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::arith_add_op { let op = self.parse_arith_add_op(inner_pairs[i].clone())?; - let right = self.parse_arith_term(inner_pairs[i + 1].clone())?; + let right = self.parse_arith_term( + inner_pairs + .get(i + 1) + .ok_or_else(|| grammar_error("arith_expr.operand", &pair_for_error))? + .clone(), + )?; expr = Box::new(ast::ArithExpr { pos: expr.pos, @@ -380,7 +404,12 @@ impl<'a> ParseContext<'a> { while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::arith_mul_op { let op = self.parse_arith_mul_op(inner_pairs[i].clone())?; - let right_unit = self.parse_expr_unit(inner_pairs[i + 1].clone())?; + let right_unit = self.parse_expr_unit( + inner_pairs + .get(i + 1) + .ok_or_else(|| grammar_error("arith_term.operand", &pair_for_error))? + .clone(), + )?; let right = Box::new(ast::ArithExpr { pos: right_unit.pos, inner: ast::ArithExprInner::ExprUnit(right_unit), diff --git a/src/parser/stmt.rs b/src/parser/stmt.rs index 7c16c63..b9d5178 100644 --- a/src/parser/stmt.rs +++ b/src/parser/stmt.rs @@ -1,3 +1,11 @@ +//! Parsing of TeaLang statement rules. +//! +//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! that lower statement-oriented parse-tree nodes into the corresponding AST +//! types: code blocks, assignments, call statements, `if`/`else` +//! conditionals, `while` loops, and `return`/`continue`/`break`/null +//! statements. + use crate::ast; use super::ParseContext; From 623687bdbb1502194cf7230e8c35fe549091a6b3 Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sat, 1 Aug 2026 18:48:01 +0800 Subject: [PATCH 2/2] docs(parser): discipline comments per comment spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removed ~28 restatement/narration comments (§5.1/§2.2): seed/walk/consume loop narrations, guard/walk/remove-parens inline restatements, arm-label duplicates, and 4 whole doc comments that only restated signatures - removed 37 "# Arguments: * pair ..." boilerplate blocks (§2.2), folding the few that carried information (pair layout, error-source) into prose - rewrote ~35 comments for audience/tense/economy (§5.5/§3/§8.2): "you must call" recast declaratively, hedge "this should not occur" stated as fact, multi-clause WHAT-narration folded into present-tense contract statements - sync fixes (§4.1): wrong doc link ast::BoolUnit::ComExpr -> ast::BoolUnitInner::ComExpr, dropped unverifiable "in order of precedence" claim in parse_expr_unit, trimmed ParseContext doc that referenced the dead input field - fixed 24 broken intra-doc links: 20 unresolved Error::Grammar (now explicit super::common::Error::Grammar targets), 3 redundant explicit link targets in the PR-added module docs, 1 wrong variant path; parser doc warnings 24 -> 0 - no TODO/FIXME tags, divider comments, or commented-out code in scope --- src/parser.rs | 39 +++------ src/parser/common.rs | 19 ++-- src/parser/decl.rs | 112 ++++++------------------ src/parser/expr.rs | 201 +++++++++++-------------------------------- src/parser/stmt.rs | 56 ++++-------- 5 files changed, 116 insertions(+), 311 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 96eebeb..00f8746 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,10 +1,10 @@ //! Parser module for the TeaLang compiler front-end. //! -//! This module is responsible for transforming a raw TeaLang source string into -//! a typed Abstract Syntax Tree (AST). It uses the [pest] PEG parser generator -//! to tokenise and structurally parse the source according to the grammar -//! defined in `tealang.pest`, and then walks the resulting parse tree to build -//! the AST types defined in [`crate::ast`]. +//! Transforms a raw TeaLang source string into a typed Abstract Syntax Tree +//! (AST). The [pest] PEG parser generator tokenises and structurally parses +//! the source according to the grammar defined in `tealang.pest`; the +//! resulting parse tree is then lowered into the AST types defined in +//! [`crate::ast`]. //! //! # Main entry points //! * [`Parser`] – the public façade that implements [`crate::common::Generator`]. @@ -34,10 +34,9 @@ use self::common::{grammar_error_static, ParseResult, Rule, TeaLangParser}; /// Public parser that turns a TeaLang source string into an AST. /// -/// After construction with [`Parser::new`] you must call -/// [`Generator::generate`] before accessing the [`Parser::program`] field. +/// [`Generator::generate`] must be called after construction before +/// accessing the [`Parser::program`] field. pub struct Parser<'a> { - /// The raw TeaLang source text to be parsed. input: &'a str, /// The parsed AST program, populated by [`Generator::generate`]. /// `None` until `generate` completes successfully. @@ -47,8 +46,8 @@ pub struct Parser<'a> { impl<'a> Parser<'a> { /// Creates a new `Parser` for the given source string. /// - /// The parser is not yet run; call [`Generator::generate`] to perform - /// parsing and populate [`Parser::program`]. + /// Parsing is not run yet; [`Generator::generate`] performs parsing and + /// populates [`Parser::program`]. pub fn new(input: &'a str) -> Self { Self { input, @@ -78,7 +77,6 @@ impl<'a> Generator for Parser<'a> { let ast = self .program .as_ref() - // Guard: generate() must be called before output(). .ok_or_else(|| grammar_error_static("output before generate"))?; write!(w, "{ast}")?; Ok(()) @@ -86,33 +84,21 @@ impl<'a> Generator for Parser<'a> { } /// Internal context that owns a single parse pass over one source string. -/// -/// `ParseContext` is constructed by [`Parser`] and carries the source slice so -/// that all parser helper methods can reference it if needed. pub(crate) struct ParseContext<'a> { #[allow(dead_code)] - /// The original source text being parsed. input: &'a str, } impl<'a> ParseContext<'a> { - /// Creates a new `ParseContext` for the given source string. fn new(input: &'a str) -> Self { Self { input } } - /// Parses the full source string into a boxed [`ast::Program`]. - /// - /// Uses [`TeaLangParser`] to produce a parse tree for the `program` rule, - /// then iterates over top-level nodes to collect `use` statements and - /// program elements (variable declarations, struct definitions, function - /// declarations and definitions). fn parse(&self) -> ParseResult> { - // Run the pest parser; convert any pest::Error into Error::Syntax. let pairs = >::parse(Rule::program, self.input) .map_err(|e| { - // Pest reports either a single position or a span start/end; - // use the start position for the structured fields. + // Pest reports either a single position or a span; the span's + // start position populates the structured fields. let (line, column) = match e.line_col { pest::error::LineColLocation::Pos(pos) => pos, pest::error::LineColLocation::Span(start, _) => start, @@ -129,7 +115,6 @@ impl<'a> ParseContext<'a> { for pair in pairs { if pair.as_rule() == Rule::program { - // Walk the top-level children of the `program` node. for inner in pair.into_inner() { match inner.as_rule() { Rule::use_stmt => { @@ -140,7 +125,7 @@ impl<'a> ParseContext<'a> { elements.push(*elem); } } - // End-of-input marker; nothing to do. + // End-of-input marker. Rule::EOI => {} _ => {} } diff --git a/src/parser/common.rs b/src/parser/common.rs index 6110f95..57514f5 100644 --- a/src/parser/common.rs +++ b/src/parser/common.rs @@ -60,13 +60,9 @@ pub enum Error { pub(crate) struct TeaLangParser; /// A specialized `Result` type used throughout the parser. -/// `Ok` carries a successfully parsed value of type `T`; `Err` carries an -/// [`Error`] describing what went wrong. pub(crate) type ParseResult = Result; /// A single node in the pest parse tree, parameterised by the input lifetime. -/// This is a type alias for [`pest::iterators::Pair`] bound to the [`Rule`] -/// enum produced by [`TeaLangParser`]. pub(crate) type Pair<'a> = pest::iterators::Pair<'a, Rule>; /// Collapses a raw source snippet into a compact, single-line preview string @@ -78,9 +74,7 @@ pub(crate) type Pair<'a> = pest::iterators::Pair<'a, Rule>; pub(crate) fn compact_snippet(snippet: &str) -> String { const MAX_CHARS: usize = 48; - // Collapse all whitespace sequences into a single space. let compact = snippet.split_whitespace().collect::>().join(" "); - // Fall back to trimming if the split produced nothing (e.g., all whitespace). let normalized = if compact.is_empty() { snippet.trim().to_string() } else { @@ -91,7 +85,6 @@ pub(crate) fn compact_snippet(snippet: &str) -> String { return "".to_string(); } - // Take up to MAX_CHARS characters; append "..." if the string is longer. let mut chars = normalized.chars(); let preview: String = chars.by_ref().take(MAX_CHARS).collect(); if chars.next().is_some() { @@ -108,7 +101,6 @@ pub(crate) fn compact_snippet(snippet: &str) -> String { /// or function where the unexpected structure was encountered. pub(crate) fn grammar_error(context: &'static str, pair: &Pair<'_>) -> Error { let span = pair.as_span(); - // Extract line and column numbers from the start of the span. let (line, column) = span.start_pos().line_col(); let near = compact_snippet(span.as_str()); @@ -133,17 +125,16 @@ pub(crate) fn grammar_error_static(context: &'static str) -> Error { } } -/// Returns the byte offset of the start of `pair`'s span within the source -/// string. This is used to track source positions in AST nodes. +/// Byte offset of the start of `pair`'s span; recorded as the source position +/// of AST nodes. pub(crate) fn get_pos(pair: &Pair<'_>) -> usize { pair.as_span().start() } -/// Parses an integer literal from a `num` parse-tree node. +/// Parses the text of a `num` parse-tree node as an `i32`. /// -/// Reads the raw text of `pair`, attempts to parse it as an `i32`, and wraps -/// any failure in [`Error::InvalidNumber`] that includes the literal text and -/// its source position. +/// Failures return [`Error::InvalidNumber`] with the literal text and its +/// source position. pub(crate) fn parse_num(pair: Pair) -> ParseResult { let literal = pair.as_str().to_string(); let (line, column) = pair.as_span().start_pos().line_col(); diff --git a/src/parser/decl.rs b/src/parser/decl.rs index 6bb9582..a0f54a2 100644 --- a/src/parser/decl.rs +++ b/src/parser/decl.rs @@ -1,6 +1,6 @@ //! Parsing of TeaLang declaration and definition rules. //! -//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! This submodule implements the [`ParseContext`] methods //! that lower declaration-oriented parse-tree nodes into the corresponding AST //! types: `use` statements, program elements, struct definitions, typed //! variable declarations and definitions (including array initialisers), and @@ -12,19 +12,9 @@ use super::common::{get_pos, grammar_error, parse_num, Pair, ParseResult, Rule}; use super::ParseContext; impl<'a> ParseContext<'a> { - /// Parses a `use_stmt` parse-tree node into an [`ast::UseStmt`]. - /// - /// A `use` statement has the form `use module::path;`. The method collects - /// all `identifier` children and joins them with `"::"` to reconstruct the - /// fully-qualified module path. - /// - /// # Arguments - /// * `pair` – the `use_stmt` parse-tree node. - /// - /// # Returns - /// An [`ast::UseStmt`] containing the module path string. + /// Parses a `use_stmt` parse-tree node (`use module::path;`) into an + /// [`ast::UseStmt`]. pub(crate) fn parse_use_stmt(&self, pair: Pair) -> ParseResult { - // Collect every identifier segment from the use path. let parts: Vec<&str> = pair .into_inner() .filter(|p| p.as_rule() == Rule::identifier) @@ -39,11 +29,8 @@ impl<'a> ParseContext<'a> { /// /// A program element is one of: a variable declaration statement, a struct /// definition, a function declaration statement, or a function definition. - /// Returns `None` if the node contains no recognisable inner rule (this - /// should not occur in a well-formed parse tree). - /// - /// # Arguments - /// * `pair` – the `program_element` parse-tree node. + /// Returns `None` if the node contains no recognised inner rule; a + /// well-formed parse tree never triggers this. pub(crate) fn parse_program_element( &self, pair: Pair, @@ -80,14 +67,9 @@ impl<'a> ParseContext<'a> { Ok(None) } - /// Parses a `struct_def` node into a boxed [`ast::StructDef`]. - /// - /// A struct definition has the form `struct Name { field_list }`. The - /// method extracts the struct name and delegates field parsing to + /// Parses a `struct_def` node (`struct Name { field_list }`) into a boxed + /// [`ast::StructDef`]; field parsing delegates to /// [`Self::parse_typed_var_decl_list`]. - /// - /// # Arguments - /// * `pair` – the `struct_def` parse-tree node. pub(crate) fn parse_struct_def(&self, pair: Pair) -> ParseResult> { let mut identifier = String::new(); let mut decls = Vec::new(); @@ -103,12 +85,6 @@ impl<'a> ParseContext<'a> { Ok(Box::new(ast::StructDef { identifier, decls })) } - /// Parses a `typed_var_decl_list` node into a `Vec` of [`ast::VarDecl`]. - /// - /// Each child `typed_var_decl` node is delegated to [`Self::parse_var_decl`]. - /// - /// # Arguments - /// * `pair` – the `typed_var_decl_list` parse-tree node. pub(crate) fn parse_typed_var_decl_list(&self, pair: Pair) -> ParseResult> { let mut decls = Vec::new(); for inner in pair.into_inner() { @@ -119,16 +95,15 @@ impl<'a> ParseContext<'a> { Ok(decls) } - /// Parses a `typed_var_decl` (or `var_decl`) node into a boxed [`ast::VarDecl`]. - /// - /// Extracts the variable name, an optional type specifier, and—for array - /// declarations—the array length. Returns [`Error::Grammar`] if the - /// identifier is missing. + /// Parses a `typed_var_decl` (or `var_decl`) node into a boxed + /// [`ast::VarDecl`]: variable name, optional type specifier, and — for + /// array declarations — the array length. /// - /// # Arguments - /// * `pair` – the `typed_var_decl` / `var_decl` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if the + /// identifier child is missing. pub(crate) fn parse_var_decl(&self, pair: Pair) -> ParseResult> { - // Keep a clone to pass to grammar_error if needed. + // Cloned because into_inner() consumes `pair`; error reporting still + // needs the node afterwards. let pair_for_error = pair.clone(); let mut identifier: Option = None; let mut type_specifier: Option = None; @@ -153,7 +128,6 @@ impl<'a> ParseContext<'a> { let identifier = identifier.ok_or_else(|| grammar_error("var_decl.identifier", &pair_for_error))?; - // Build the inner variant based on whether an array length was found. let inner = if let Some(len) = array_len { ast::VarDeclInner::Array(Box::new(ast::VarDeclArray { len })) } else { @@ -172,14 +146,11 @@ impl<'a> ParseContext<'a> { /// Recognises reference types (`&T`), the built-in `i32` keyword, and /// user-defined composite (struct) types by their identifier. Returns /// `Ok(None)` when the node is empty or contains no recognised type rule, - /// and [`Error::Grammar`] if a reference type is missing or has an empty - /// inner type specifier. - /// - /// # Arguments - /// * `pair` – the `type_spec` parse-tree node. + /// and [`Error::Grammar`](super::common::Error::Grammar) if a reference + /// type is missing or has an empty inner type specifier. pub(crate) fn parse_type_spec(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); - // Record the start position for use in the returned AST node. + // Captured before `into_inner` consumes `pair`. let pos = get_pos(&pair); let children: Vec<_> = pair.into_inner().collect(); @@ -202,14 +173,12 @@ impl<'a> ParseContext<'a> { })); } Rule::kw_i32 => { - // Built-in integer type. return Ok(Some(ast::TypeSpecifier { pos, inner: ast::TypeSpecifierInner::BuiltIn(ast::BuiltIn::Int), })); } Rule::identifier => { - // User-defined composite (struct) type referenced by name. return Ok(Some(ast::TypeSpecifier { pos, inner: ast::TypeSpecifierInner::Composite(child.as_str().to_string()), @@ -226,11 +195,9 @@ impl<'a> ParseContext<'a> { /// /// A variable declaration statement is either a variable definition /// (`var_def`, e.g. `let x = 1;`) or a plain declaration (`var_decl`, - /// e.g. `let x: i32;`). Returns [`Error::Grammar`] if neither child is + /// e.g. `let x: i32;`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither child is /// present. - /// - /// # Arguments - /// * `pair` – the `var_decl_stmt` parse-tree node. pub(crate) fn parse_var_decl_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -258,9 +225,6 @@ impl<'a> ParseContext<'a> { /// contains an `array_initializer` child; a scalar definition contains a /// `right_val` child. The type annotation (after `:`) is optional in both /// forms. - /// - /// # Arguments - /// * `pair` – the `var_def` parse-tree node. pub(crate) fn parse_var_def(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let inner_pairs: Vec<_> = pair.into_inner().collect(); @@ -272,7 +236,6 @@ impl<'a> ParseContext<'a> { .as_str() .to_string(); - // Determine the form of the definition by looking for key child rules. let has_initializer = inner_pairs .iter() .any(|p| p.as_rule() == Rule::array_initializer); @@ -288,7 +251,6 @@ impl<'a> ParseContext<'a> { .clone(), )? as usize; - // Type annotation is optional; only present when a colon was found. let type_specifier = if has_colon { self.parse_type_spec( inner_pairs @@ -349,14 +311,10 @@ impl<'a> ParseContext<'a> { /// Two forms are supported: /// * **Explicit list** – `[v0, v1, v2]`: contains a `right_val_list`. /// * **Fill** – `[v; N]`: contains a single `right_val` and a `num`. - /// - /// # Arguments - /// * `pair` – the `array_initializer` parse-tree node. fn parse_array_initializer(&self, pair: Pair) -> ParseResult { let pair_for_error = pair.clone(); let children: Vec<_> = pair.into_inner().collect(); - // Check for the explicit-list form first. if let Some(list_pair) = children .iter() .find(|p| p.as_rule() == Rule::right_val_list) @@ -365,7 +323,7 @@ impl<'a> ParseContext<'a> { return Ok(ast::ArrayInitializer::ExplicitList(vals)); } - // Otherwise it must be the fill form `[val; count]`. + // Fill form `[val; count]`. let val_pair = children .iter() .find(|p| p.as_rule() == Rule::right_val) @@ -384,10 +342,8 @@ impl<'a> ParseContext<'a> { /// Parses a `fn_decl_stmt` node into a boxed [`ast::FnDeclStmt`]. /// /// A function declaration statement wraps a single `fn_decl` child. - /// Returns [`Error::Grammar`] if the expected child is absent. - /// - /// # Arguments - /// * `pair` – the `fn_decl_stmt` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if the + /// expected child is absent. pub(crate) fn parse_fn_decl_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -401,13 +357,6 @@ impl<'a> ParseContext<'a> { Err(grammar_error("fn_decl_stmt", &pair_for_error)) } - /// Parses a `fn_decl` node into a boxed [`ast::FnDecl`]. - /// - /// Extracts the function name, an optional parameter list, and an optional - /// return type specifier. - /// - /// # Arguments - /// * `pair` – the `fn_decl` parse-tree node. fn parse_fn_decl(&self, pair: Pair) -> ParseResult> { let mut identifier = String::new(); let mut param_decl = None; @@ -432,12 +381,10 @@ impl<'a> ParseContext<'a> { /// Parses a `param_decl` node into a boxed [`ast::ParamDecl`]. /// - /// A parameter declaration consists of a `typed_var_decl_list` that lists - /// all formal parameters with their types. Returns [`Error::Grammar`] if - /// the expected child is absent. - /// - /// # Arguments - /// * `pair` – the `param_decl` parse-tree node. + /// A parameter declaration wraps a `typed_var_decl_list` holding the + /// formal parameters with their types. Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if the expected child + /// is absent. fn parse_param_decl(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -454,10 +401,8 @@ impl<'a> ParseContext<'a> { /// /// A function definition contains a `fn_decl` header followed by one or /// more `code_block_stmt` nodes that form the function body. Returns - /// [`Error::Grammar`] if the `fn_decl` child is absent. - /// - /// # Arguments - /// * `pair` – the `fn_def` parse-tree node. + /// [`Error::Grammar`](super::common::Error::Grammar) if the `fn_decl` + /// child is absent. pub(crate) fn parse_fn_def(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let mut fn_decl = None; @@ -466,7 +411,6 @@ impl<'a> ParseContext<'a> { for inner in pair.into_inner() { match inner.as_rule() { Rule::fn_decl => fn_decl = Some(self.parse_fn_decl(inner)?), - // Each statement in the body is collected in order. Rule::code_block_stmt => stmts.push(*self.parse_code_block_stmt(inner)?), _ => {} } diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 1761393..b5cb17d 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -1,6 +1,6 @@ //! Parsing of TeaLang expression rules. //! -//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! This submodule implements the [`ParseContext`] methods //! that lower expression-oriented parse-tree nodes into the corresponding AST //! types: right-hand values, Boolean expressions (`||`, `&&`, `!`, and //! comparisons), arithmetic expressions (the `+`/`-` and `*`/`/` precedence @@ -13,13 +13,6 @@ use super::common::{get_pos, grammar_error, parse_num, Pair, ParseResult, Rule}; use super::ParseContext; impl<'a> ParseContext<'a> { - /// Parses a `right_val_list` node into a `Vec` of [`ast::RightVal`]. - /// - /// Iterates over every `right_val` child and delegates to - /// [`Self::parse_right_val`]. - /// - /// # Arguments - /// * `pair` – the `right_val_list` parse-tree node. pub(crate) fn parse_right_val_list(&self, pair: Pair) -> ParseResult> { let mut vals = Vec::new(); for inner in pair.into_inner() { @@ -33,11 +26,8 @@ impl<'a> ParseContext<'a> { /// Parses a `right_val` node into a boxed [`ast::RightVal`]. /// /// A right-hand-side value is either a Boolean expression (`bool_expr`) or - /// an arithmetic expression (`arith_expr`). Returns [`Error::Grammar`] if - /// neither is found. - /// - /// # Arguments - /// * `pair` – the `right_val` parse-tree node. + /// an arithmetic expression (`arith_expr`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither is found. pub(crate) fn parse_right_val(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -62,11 +52,8 @@ impl<'a> ParseContext<'a> { /// Parses a `bool_expr` node into a boxed [`ast::BoolExpr`]. /// /// A Boolean expression is a sequence of `bool_and_term` nodes optionally - /// combined with `||` operators. The method builds a left-associative tree - /// of [`ast::BoolBiOpExpr`] nodes with [`ast::BoolBiOp::Or`]. - /// - /// # Arguments - /// * `pair` – the `bool_expr` parse-tree node. + /// combined with `||` operators, lowered into a left-associative tree of + /// [`ast::BoolBiOpExpr`] nodes with [`ast::BoolBiOp::Or`]. pub(crate) fn parse_bool_expr(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let inner_pairs: Vec<_> = pair.into_inner().collect(); @@ -75,14 +62,11 @@ impl<'a> ParseContext<'a> { return Err(grammar_error("bool_expr", &pair_for_error)); } - // Seed the accumulator with the first term. let mut expr = self.parse_bool_and_term(inner_pairs[0].clone())?; - // Walk through the remaining pairs looking for `||` operators. let mut i = 1; while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::op_or { - // Consume the operator and the next operand together. let right = self.parse_bool_and_term( inner_pairs .get(i + 1) @@ -109,11 +93,8 @@ impl<'a> ParseContext<'a> { /// Parses a `bool_and_term` node into a boxed [`ast::BoolExpr`]. /// /// A Boolean AND term is a sequence of `bool_unit_atom` nodes optionally - /// combined with `&&` operators. The method builds a left-associative tree - /// of [`ast::BoolBiOpExpr`] nodes with [`ast::BoolBiOp::And`]. - /// - /// # Arguments - /// * `pair` – the `bool_and_term` parse-tree node. + /// combined with `&&` operators, lowered into a left-associative tree of + /// [`ast::BoolBiOpExpr`] nodes with [`ast::BoolBiOp::And`]. fn parse_bool_and_term(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let inner_pairs: Vec<_> = pair.into_inner().collect(); @@ -122,14 +103,12 @@ impl<'a> ParseContext<'a> { return Err(grammar_error("bool_and_term", &pair_for_error)); } - // Seed the accumulator with the first unit wrapped in a BoolUnit variant. let first_unit = self.parse_bool_unit_atom(inner_pairs[0].clone())?; let mut expr = Box::new(ast::BoolExpr { pos: first_unit.pos, inner: ast::BoolExprInner::BoolUnit(first_unit), }); - // Walk through the remaining pairs looking for `&&` operators. let mut i = 1; while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::op_and { @@ -167,9 +146,6 @@ impl<'a> ParseContext<'a> { /// 1. A prefixed `!` (NOT) operator followed by a nested `bool_unit_atom`. /// 2. A parenthesised Boolean expression (`bool_unit_paren`). /// 3. A comparison expression (`bool_comparison`). - /// - /// # Arguments - /// * `pair` – the `bool_unit_atom` parse-tree node. fn parse_bool_unit_atom(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let pos = get_pos(&pair); @@ -206,18 +182,14 @@ impl<'a> ParseContext<'a> { /// /// After stripping the surrounding parentheses, the inner content is /// either: - /// * A single `bool_expr` — wrapped as a `BoolUnit::BoolExpr`. + /// * A single `bool_expr` — wrapped as [`ast::BoolUnitInner::BoolExpr`]. /// * A comparison triple `(expr op expr)` — delegated to /// [`Self::parse_comparison_pair_triple`]. - /// - /// # Arguments - /// * `pair` – the `bool_unit_paren` parse-tree node. fn parse_bool_unit_paren(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let pos = get_pos(&pair); let inner_pairs: Vec<_> = pair.into_inner().collect(); - // Remove parenthesis tokens; keep only meaningful children. let filtered: Vec<_> = inner_pairs .into_iter() .filter(|p| p.as_rule() != Rule::lparen && p.as_rule() != Rule::rparen) @@ -230,17 +202,11 @@ impl<'a> ParseContext<'a> { })); } - // Otherwise treat the filtered children as a comparison triple. self.parse_comparison_pair_triple(pos, &filtered, "bool_unit_paren", &pair_for_error) } - /// Parses a `bool_comparison` node into a boxed [`ast::BoolUnit`]. - /// - /// A comparison has the form `expr op expr` (exactly three children). - /// Delegates directly to [`Self::parse_comparison_pair_triple`]. - /// - /// # Arguments - /// * `pair` – the `bool_comparison` parse-tree node. + /// Parses a `bool_comparison` node (`expr op expr`, exactly three + /// children) into a boxed [`ast::BoolUnit`]. fn parse_bool_comparison(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let pos = get_pos(&pair); @@ -248,17 +214,11 @@ impl<'a> ParseContext<'a> { self.parse_comparison_pair_triple(pos, &inner_pairs, "bool_comparison", &pair_for_error) } - /// Validates that `pairs` contains exactly three elements and builds a - /// comparison [`ast::BoolUnit`] from them. + /// Builds a comparison [`ast::BoolUnit`] from `pairs`, which must be laid + /// out as `[left_expr, comp_op, right_expr]`. /// - /// Returns [`Error::Grammar`] (using `context` as the label) when the - /// slice does not have exactly three elements. - /// - /// # Arguments - /// * `pos` – source byte offset for the resulting AST node. - /// * `pairs` – slice expected to contain `[left_expr, comp_op, right_expr]`. - /// * `context` – human-readable context label used in error messages. - /// * `pair_for_error` – original parse-tree node used if an error is raised. + /// Returns [`Error::Grammar`](super::common::Error::Grammar), labelled + /// with `context`, when the slice does not have exactly three elements. fn parse_comparison_pair_triple( &self, pos: usize, @@ -278,16 +238,9 @@ impl<'a> ParseContext<'a> { ) } - /// Builds a [`ast::BoolUnit::ComExpr`] from three parse-tree nodes. - /// - /// Parses the left operand, comparison operator, and right operand in turn - /// and assembles them into a [`ast::ComExpr`]. - /// - /// # Arguments - /// * `pos` – source byte offset for the resulting AST node. - /// * `left_pair` – parse-tree node for the left `expr_unit`. - /// * `op_pair` – parse-tree node for the comparison operator. - /// * `right_pair` – parse-tree node for the right `expr_unit`. + /// Builds a comparison [`ast::BoolUnit`] + /// ([`ast::BoolUnitInner::ComExpr`]) from pairs for the left operand, the + /// comparison operator, and the right operand. fn parse_comparison_to_bool_unit( &self, pos: usize, @@ -305,13 +258,10 @@ impl<'a> ParseContext<'a> { })) } - /// Parses a `comp_op` node into an [`ast::ComOp`] variant. - /// - /// Recognises the six comparison operators: `<`, `>`, `<=`, `>=`, `==`, - /// `!=`. Returns [`Error::Grammar`] if no known operator token is found. - /// - /// # Arguments - /// * `pair` – the `comp_op` parse-tree node. + /// Maps a `comp_op` node onto one of the six comparison operators: `<`, + /// `>`, `<=`, `>=`, `==`, `!=`. Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if no known operator + /// token is found. fn parse_comp_op(&self, pair: Pair) -> ParseResult { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -331,11 +281,8 @@ impl<'a> ParseContext<'a> { /// Parses an `arith_expr` node into a boxed [`ast::ArithExpr`]. /// /// An arithmetic expression is a sequence of `arith_term` nodes optionally - /// combined with additive operators (`+`, `-`). The method builds a + /// combined with additive operators (`+`, `-`), lowered into a /// left-associative tree of [`ast::ArithBiOpExpr`] nodes. - /// - /// # Arguments - /// * `pair` – the `arith_expr` parse-tree node. pub(crate) fn parse_arith_expr(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let inner_pairs: Vec<_> = pair.into_inner().collect(); @@ -344,10 +291,8 @@ impl<'a> ParseContext<'a> { return Err(grammar_error("arith_expr", &pair_for_error)); } - // Seed the accumulator with the first term. let mut expr = self.parse_arith_term(inner_pairs[0].clone())?; - // Walk through the remaining pairs looking for additive operators. let mut i = 1; while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::arith_add_op { @@ -379,11 +324,8 @@ impl<'a> ParseContext<'a> { /// Parses an `arith_term` node into a boxed [`ast::ArithExpr`]. /// /// An arithmetic term is a sequence of `expr_unit` nodes optionally - /// combined with multiplicative operators (`*`, `/`). The method builds a + /// combined with multiplicative operators (`*`, `/`), lowered into a /// left-associative tree of [`ast::ArithBiOpExpr`] nodes. - /// - /// # Arguments - /// * `pair` – the `arith_term` parse-tree node. fn parse_arith_term(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let inner_pairs: Vec<_> = pair.into_inner().collect(); @@ -392,14 +334,12 @@ impl<'a> ParseContext<'a> { return Err(grammar_error("arith_term", &pair_for_error)); } - // Seed the accumulator with the first expression unit. let first_unit = self.parse_expr_unit(inner_pairs[0].clone())?; let mut expr = Box::new(ast::ArithExpr { pos: first_unit.pos, inner: ast::ArithExprInner::ExprUnit(first_unit), }); - // Walk through the remaining pairs looking for multiplicative operators. let mut i = 1; while i < inner_pairs.len() { if inner_pairs[i].as_rule() == Rule::arith_mul_op { @@ -432,13 +372,10 @@ impl<'a> ParseContext<'a> { Ok(expr) } - /// Parses an `arith_add_op` node into an [`ast::ArithBiOp`] additive variant. - /// - /// Recognises `+` and `-` tokens. Returns [`Error::Grammar`] if neither - /// is found. - /// - /// # Arguments - /// * `pair` – the `arith_add_op` parse-tree node. + /// Maps an `arith_add_op` node onto the matching additive + /// [`ast::ArithBiOp`] variant (`+` or `-`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither token is + /// found. fn parse_arith_add_op(&self, pair: Pair) -> ParseResult { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -451,13 +388,10 @@ impl<'a> ParseContext<'a> { Err(grammar_error("arith_add_op", &pair_for_error)) } - /// Parses an `arith_mul_op` node into an [`ast::ArithBiOp`] multiplicative variant. - /// - /// Recognises `*` and `/` tokens. Returns [`Error::Grammar`] if neither - /// is found. - /// - /// # Arguments - /// * `pair` – the `arith_mul_op` parse-tree node. + /// Maps an `arith_mul_op` node onto the matching multiplicative + /// [`ast::ArithBiOp`] variant (`*` or `/`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither token is + /// found. fn parse_arith_mul_op(&self, pair: Pair) -> ParseResult { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -473,8 +407,7 @@ impl<'a> ParseContext<'a> { /// Parses an `expr_unit` node into a boxed [`ast::ExprUnit`]. /// /// An expression unit is the atomic building block of arithmetic - /// expressions. The method handles the following forms, in order of - /// precedence: + /// expressions: /// 1. Negated integer literal: `-`. /// 2. Parenthesised arithmetic expression: `()`. /// 3. Function call: ``. @@ -482,16 +415,13 @@ impl<'a> ParseContext<'a> { /// 5. Reference: `&`. /// 6. Identifier with optional field/index suffixes (left-value chain). /// - /// Returns [`Error::Grammar`] if none of the forms matches. - /// - /// # Arguments - /// * `pair` – the `expr_unit` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if none of + /// the forms matches. pub(crate) fn parse_expr_unit(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let pos = get_pos(&pair); let inner_pairs: Vec<_> = pair.into_inner().collect(); - // Strip parentheses to obtain the meaningful children. let filtered: Vec<_> = inner_pairs .iter() .filter(|p| !matches!(p.as_rule(), Rule::lparen | Rule::rparen)) @@ -551,13 +481,11 @@ impl<'a> ParseContext<'a> { if !inner_pairs.is_empty() && inner_pairs[0].as_rule() == Rule::identifier { let id = inner_pairs[0].as_str().to_string(); - // Start with a plain identifier left-value. let mut base = Box::new(ast::LeftVal { pos, inner: ast::LeftValInner::Id(id), }); - // Apply any chained field/index suffixes. let mut i = 1; while i < inner_pairs.len() { match inner_pairs[i].as_rule() { @@ -578,10 +506,8 @@ impl<'a> ParseContext<'a> { /// Parses an `index_expr` node into a boxed [`ast::IndexExpr`]. /// /// An index expression is either a numeric literal (`arr[0]`) or an - /// identifier (`arr[i]`). Returns [`Error::Grammar`] if neither is found. - /// - /// # Arguments - /// * `pair` – the `index_expr` parse-tree node. + /// identifier (`arr[i]`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither is found. pub(crate) fn parse_index_expr(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -607,10 +533,9 @@ impl<'a> ParseContext<'a> { /// /// Dispatches to either [`Self::parse_module_prefixed_call`] (for calls /// like `module::func(...)`) or [`Self::parse_local_call`] (for calls like - /// `func(...)`). Returns [`Error::Grammar`] if neither child is found. - /// - /// # Arguments - /// * `pair` – the `fn_call` parse-tree node. + /// `func(...)`). Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if neither child is + /// found. pub(crate) fn parse_fn_call(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -627,14 +552,11 @@ impl<'a> ParseContext<'a> { Err(grammar_error("fn_call", &pair_for_error)) } - /// Parses a `module_prefixed_call` node into a boxed [`ast::FnCall`]. + /// Parses a `module_prefixed_call` node (`mod1::mod2::func(args)`) into a + /// boxed [`ast::FnCall`]. /// - /// A module-prefixed call has the form `mod1::mod2::func(args)`. All - /// identifier children are collected; the last one becomes the function - /// name and the rest are joined with `"::"` as the module prefix. - /// - /// # Arguments - /// * `pair` – the `module_prefixed_call` parse-tree node. + /// The last `identifier` child is the function name; the preceding ones + /// form the module path. fn parse_module_prefixed_call(&self, pair: Pair) -> ParseResult> { let inner_pairs: Vec<_> = pair.into_inner().collect(); let mut idents: Vec = Vec::new(); @@ -648,7 +570,6 @@ impl<'a> ParseContext<'a> { } } - // The last identifier is the function name; the rest form the module path. let name = idents.pop().unwrap_or_default(); let module_prefix = if idents.is_empty() { None @@ -663,13 +584,8 @@ impl<'a> ParseContext<'a> { })) } - /// Parses a `local_call` node into a boxed [`ast::FnCall`]. - /// - /// A local call has the form `func(args)` with no module prefix. The - /// method extracts the function name and the argument list. - /// - /// # Arguments - /// * `pair` – the `local_call` parse-tree node. + /// Parses a `local_call` node (`func(args)`, no module prefix) into a + /// boxed [`ast::FnCall`]. fn parse_local_call(&self, pair: Pair) -> ParseResult> { let mut name = String::new(); let mut vals = Vec::new(); @@ -695,10 +611,8 @@ impl<'a> ParseContext<'a> { /// zero or more `expr_suffix` nodes representing field access (`.field`) or /// array indexing (`[idx]`). /// - /// Returns [`Error::Grammar`] if the node contains no children. - /// - /// # Arguments - /// * `pair` – the `left_val` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if the node + /// contains no children. pub(crate) fn parse_left_val(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let pos = get_pos(&pair); @@ -716,7 +630,6 @@ impl<'a> ParseContext<'a> { inner: ast::LeftValInner::Id(id), }); - // Apply any chained field/index suffixes. let mut i = 1; while i < inner_pairs.len() { match inner_pairs[i].as_rule() { @@ -738,13 +651,7 @@ impl<'a> ParseContext<'a> { /// * An array index: `[]` → [`ast::LeftValInner::ArrayExpr`]. /// * A field access: `.` → [`ast::LeftValInner::MemberExpr`]. /// - /// Bracket and dot tokens are skipped; only semantic children are - /// processed. If no recognised suffix token is found the `base` value is - /// returned unchanged. - /// - /// # Arguments - /// * `base` – the left-value accumulated so far. - /// * `suffix` – the `expr_suffix` parse-tree node to apply. + /// If no recognised suffix token is found, `base` returns unchanged. pub(crate) fn parse_expr_suffix( &self, base: Box, @@ -754,7 +661,6 @@ impl<'a> ParseContext<'a> { for inner in suffix.into_inner() { match inner.as_rule() { - // Skip syntactic punctuation tokens. Rule::lbracket | Rule::rbracket | Rule::dot => continue, Rule::index_expr => { let idx = self.parse_index_expr(inner)?; @@ -786,25 +692,22 @@ impl<'a> ParseContext<'a> { /// Converts a [`ast::LeftVal`] into the corresponding [`ast::ExprUnit`] variant. /// -/// This free function is used when an identifier (or field/array access chain) -/// that was initially parsed as a left-value is later determined to appear on -/// the right-hand side of an expression. The conversion is infallible for the -/// three recognised [`ast::LeftValInner`] variants. +/// An identifier (or field/index access chain) is parsed as a left-value +/// first; this conversion applies when it turns out to sit on the right-hand +/// side of an expression. Infallible for the three [`ast::LeftValInner`] +/// variants. fn left_val_to_expr_unit(lval: ast::LeftVal) -> ParseResult> { let pos = lval.pos; match &lval.inner { - // Plain identifier `x` → `ExprUnit::Id`. ast::LeftValInner::Id(id) => Ok(Box::new(ast::ExprUnit { pos, inner: ast::ExprUnitInner::Id(id.clone()), })), - // Array index access `arr[i]` → `ExprUnit::ArrayExpr`. ast::LeftValInner::ArrayExpr(arr_expr) => Ok(Box::new(ast::ExprUnit { pos, inner: ast::ExprUnitInner::ArrayExpr(arr_expr.clone()), })), - // Member field access `s.f` → `ExprUnit::MemberExpr`. ast::LeftValInner::MemberExpr(mem_expr) => Ok(Box::new(ast::ExprUnit { pos, inner: ast::ExprUnitInner::MemberExpr(mem_expr.clone()), diff --git a/src/parser/stmt.rs b/src/parser/stmt.rs index b9d5178..bc82af6 100644 --- a/src/parser/stmt.rs +++ b/src/parser/stmt.rs @@ -1,6 +1,6 @@ //! Parsing of TeaLang statement rules. //! -//! This submodule implements the [`ParseContext`](super::ParseContext) methods +//! This submodule implements the [`ParseContext`] methods //! that lower statement-oriented parse-tree nodes into the corresponding AST //! types: code blocks, assignments, call statements, `if`/`else` //! conditionals, `while` loops, and `return`/`continue`/`break`/null @@ -26,10 +26,8 @@ impl<'a> ParseContext<'a> { /// * `break_stmt` → wraps a unit [`ast::BreakStmt`] /// * `null_stmt` → wraps a unit [`ast::NullStmt`] /// - /// Returns [`Error::Grammar`] if no recognisable inner rule is found. - /// - /// # Arguments - /// * `pair` – the `code_block_stmt` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if no + /// recognisable inner rule is found. pub(crate) fn parse_code_block_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -90,13 +88,12 @@ impl<'a> ParseContext<'a> { Err(grammar_error("code_block_stmt", &pair_for_error)) } - /// Parses an `assignment_stmt` node into a boxed [`ast::AssignmentStmt`]. - /// - /// An assignment has the form `left_val = right_val;`. Both operands are - /// required; [`Error::Grammar`] is returned if either is absent. + /// Parses an `assignment_stmt` node (`left_val = right_val;`) into a boxed + /// [`ast::AssignmentStmt`]. /// - /// # Arguments - /// * `pair` – the `assignment_stmt` parse-tree node. + /// Both operands are required; + /// [`Error::Grammar`](super::common::Error::Grammar) is returned if either + /// is absent. fn parse_assignment_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let mut left_val = None; @@ -121,11 +118,9 @@ impl<'a> ParseContext<'a> { /// Parses a `call_stmt` node into a boxed [`ast::CallStmt`]. /// /// A call statement is a standalone function call used for its side - /// effects: `func(args);`. Returns [`Error::Grammar`] if the expected + /// effects: `func(args);`. Returns + /// [`Error::Grammar`](super::common::Error::Grammar) if the expected /// `fn_call` child is absent. - /// - /// # Arguments - /// * `pair` – the `call_stmt` parse-tree node. fn parse_call_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); for inner in pair.into_inner() { @@ -142,10 +137,7 @@ impl<'a> ParseContext<'a> { /// Parses a `return_stmt` node into a boxed [`ast::ReturnStmt`]. /// /// The return value is optional: `return;` and `return expr;` are both - /// valid. When present, the expression is parsed as a `right_val`. - /// - /// # Arguments - /// * `pair` – the `return_stmt` parse-tree node. + /// valid. fn parse_return_stmt(&self, pair: Pair) -> ParseResult> { let mut val = None; @@ -165,20 +157,17 @@ impl<'a> ParseContext<'a> { /// if { } [else { }] /// ``` /// The condition is parsed as a `bool_expr` wrapped in a `BoolUnit`. - /// Body statements are collected into `if_stmts`; once the `else` keyword - /// token is encountered subsequent `code_block_stmt` nodes are collected - /// into `else_stmts`. + /// The grammar has no dedicated `else` node: body statements accumulate in + /// `if_stmts` until the `kw_else` token flips accumulation into + /// `else_stmts`. /// - /// Returns [`Error::Grammar`] if no condition is found. - /// - /// # Arguments - /// * `pair` – the `if_stmt` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if no + /// condition is found. fn parse_if_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let mut bool_unit = None; let mut if_stmts = Vec::new(); let mut else_stmts = None; - // Track whether we have passed the `else` keyword. let mut in_else = false; for inner in pair.into_inner() { @@ -186,7 +175,6 @@ impl<'a> ParseContext<'a> { Rule::bool_expr => { let pos = get_pos(&inner); let bool_expr = self.parse_bool_expr(inner)?; - // Wrap the condition expression in a BoolUnit node. bool_unit = Some(Box::new(ast::BoolUnit { pos, inner: ast::BoolUnitInner::BoolExpr(bool_expr), @@ -194,14 +182,12 @@ impl<'a> ParseContext<'a> { } Rule::code_block_stmt => { if in_else { - // Append to the else branch, creating the Vec on first use. let else_branch = else_stmts.get_or_insert_with(Vec::new); else_branch.push(*self.parse_code_block_stmt(inner)?); } else { if_stmts.push(*self.parse_code_block_stmt(inner)?); } } - // The `else` keyword marks the start of the else branch. Rule::kw_else => { in_else = true; } @@ -222,13 +208,10 @@ impl<'a> ParseContext<'a> { /// ```text /// while { } /// ``` - /// The condition is parsed as a `bool_expr` wrapped in a `BoolUnit` and - /// all body statements are collected in order. - /// - /// Returns [`Error::Grammar`] if no condition is found. + /// The condition is parsed as a `bool_expr` wrapped in a `BoolUnit`. /// - /// # Arguments - /// * `pair` – the `while_stmt` parse-tree node. + /// Returns [`Error::Grammar`](super::common::Error::Grammar) if no + /// condition is found. fn parse_while_stmt(&self, pair: Pair) -> ParseResult> { let pair_for_error = pair.clone(); let mut bool_unit = None; @@ -239,7 +222,6 @@ impl<'a> ParseContext<'a> { Rule::bool_expr => { let pos = get_pos(&inner); let bool_expr = self.parse_bool_expr(inner)?; - // Wrap the condition expression in a BoolUnit node. bool_unit = Some(Box::new(ast::BoolUnit { pos, inner: ast::BoolUnitInner::BoolExpr(bool_expr),