erick-alcachofa 30d64d9b65
fix(parser): support empty blocks, support nested scoping, and refine loop lookahead
Signed-off-by: erick-alcachofa <erick@artichoke.dev>

This commit addresses several critical issues in the recursive descent
parser, specifically regarding the handling of empty constructs,
statement termination, and AST representation of nested scopes. These
changes bring the implementation in line with the Artichoke EBNF
specification.

* **CodeBlock as Statement:** Added `CodeBlockStmtNode` to the
  `StatementNode` variant. This allows a bare `{}` to be treated as a
  valid statement, enabling manual scoping within functions.
* **Visitor Support:** Updated `toDot.cpp` (Graphviz) and `toString.cpp`
  (Pretty-print) to support the new `CodeBlockStmtNode` during AST
  traversal.

* **Empty Member Lists:** Implemented a pre-loop check for the closing
  brace `}` in `parseStruct` and `parseEnum`. This prevents the parser
  from attempting to parse members in empty declarations (e.g., `struct
  Empty {}`).
* **Diagnostic Accuracy:** Enhanced the member-parsing loop to provide
  better error context. If a member is not followed by a comma or a
  closing brace, the parser now explicitly suggests `',' or '}'` as the
  expected tokens.

* **Nested Scopes:** The parser now correctly identifies a `{` at the
  start of a statement and dispatches to `parseCodeBlock`.
* **Empty Code Blocks:** Added a guard in the block-parsing loop to
  check for `}` immediately after `{`, allowing functions or nested
  scopes to be empty.

* **C-Style For-Loops:** Replaced `match` with `matchAndConsume` for the
  initialization semicolon. This allows the parser to correctly handle
  loops where the initialization is omitted (e.g., `for (; 1; 1)`).

* **Correctness:** Resolves parser hangs or errors when encountering
  empty blocks.
* **Compliance:** Fully supports the EBNF definition of zero-or-more
  members/statements.
* **Visuals:** AST diagrams now accurately reflect nested block
  structures.
2025-12-26 00:28:18 -06:00

262 lines
6.9 KiB
C++

//============================================================================//
// //
// artichoke programming language //
// //
// Copyright (C) 2025 Erick Saul Guzman Ramos, whoami.artichoke.dev //
// //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as published //
// by the Free Software Foundation, either version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
// //
//============================================================================//
#pragma once
#include <artichoke/Parser/AST/Common.hpp>
#include <artichoke/Parser/AST/Types.hpp>
#include <artichoke/Parser/AST/Expressions.hpp>
namespace arti::lang::ast {
namespace nodes {
/* Forward declaration of types */
/* Main declaration node types */
struct CodeBlockStatement;
struct VariableDeclStatement;
struct IfStatement;
struct ElseStatement;
struct DeferStatement;
struct ErrDeferStatement;
struct ReturnStatement;
struct BreakStatement;
struct ContinueStatement;
struct MatchStatement;
struct SwitchStatement;
struct CForStatement;
struct RangeForStatement;
struct WhileStatement;
struct DoWhileStatement;
struct InfLoopStatement;
struct ExpressionStatement;
/* Helper declaration node types */
struct MatchCase;
struct SwitchCase;
} // namespace nodes
/* Public Aliases */
using CodeBlockStmtNode = Ptr<nodes::CodeBlockStatement>;
using VariableStmtNode = Ptr<nodes::VariableDeclStatement>;
using IfStmtNode = Ptr<nodes::IfStatement>;
using ElseStmtNode = Ptr<nodes::ElseStatement>;
using DeferStmtNode = Ptr<nodes::DeferStatement>;
using ErrDeferStmtNode = Ptr<nodes::ErrDeferStatement>;
using ReturnStmtNode = Ptr<nodes::ReturnStatement>;
using BreakStmtNode = Ptr<nodes::BreakStatement>;
using ContinueStmtNode = Ptr<nodes::ContinueStatement>;
using MatchStmtNode = Ptr<nodes::MatchStatement>;
using SwitchStmtNode = Ptr<nodes::SwitchStatement>;
using CForStmtNode = Ptr<nodes::CForStatement>;
using RangeForStmtNode = Ptr<nodes::RangeForStatement>;
using WhileStmtNode = Ptr<nodes::WhileStatement>;
using DoWhileStmtNode = Ptr<nodes::DoWhileStatement>;
using InfLoopStmtNode = Ptr<nodes::InfLoopStatement>;
using ExpressionStmtNode = Ptr<nodes::ExpressionStatement>;
using MatchCaseNode = Ptr<nodes::MatchCase>;
using SwitchCaseNode = Ptr<nodes::SwitchCase>;
/* Variant nodes */
using StatementNode = Variant<
VariableStmtNode,
IfStmtNode,
DeferStmtNode,
ErrDeferStmtNode,
ReturnStmtNode,
BreakStmtNode,
ContinueStmtNode,
MatchStmtNode,
SwitchStmtNode,
CForStmtNode,
RangeForStmtNode,
WhileStmtNode,
DoWhileStmtNode,
InfLoopStmtNode,
ExpressionStmtNode,
CodeBlockStmtNode
>;
using ElseBranchNode = Variant<
ElseStmtNode,
IfStmtNode
>;
using DeferableNode = Variant<
ExpressionStmtNode,
CodeBlockStmtNode
>;
using PreLoopStmtNode = Variant<
VariableStmtNode,
ExpressionStmtNode
>;
/* Node definitions */
struct nodes::CodeBlockStatement {
SourceLocation location;
Vector<StatementNode> statements;
};
struct nodes::VariableDeclStatement {
SourceLocation location;
String name;
Mutability mutability;
Optional<TypeNode> type;
Optional<ExpressionNode> initializer;
};
struct nodes::IfStatement {
SourceLocation location;
Optional<String> unwrappedVar;
ExpressionNode condition;
CodeBlockStmtNode body;
Optional<ElseBranchNode> elseBranch;
};
struct nodes::ElseStatement {
SourceLocation location;
Optional<String> unwrappedVar;
CodeBlockStmtNode body;
};
struct nodes::DeferStatement {
SourceLocation location;
DeferableNode body;
};
struct nodes::ErrDeferStatement {
SourceLocation location;
DeferableNode body;
};
struct nodes::ReturnStatement {
SourceLocation location;
Optional<ExpressionNode> value;
};
struct nodes::BreakStatement {
SourceLocation location;
Optional<String> label;
};
struct nodes::ContinueStatement {
SourceLocation location;
Optional<String> label;
};
struct nodes::MatchStatement {
SourceLocation location;
ExpressionNode value;
Vector<MatchCaseNode> matchCases;
Optional<CodeBlockStmtNode> defaultCase;
};
struct nodes::SwitchStatement {
SourceLocation location;
ExpressionNode value;
Vector<SwitchCaseNode> switchCases;
Optional<CodeBlockStmtNode> defaultCase;
};
struct nodes::CForStatement {
SourceLocation location;
Optional<String> label;
Optional<PreLoopStmtNode> preLoop;
ExpressionNode condition;
Optional<ExpressionNode> postLoop;
CodeBlockStmtNode body;
};
struct nodes::RangeForStatement {
SourceLocation location;
Optional<String> label;
String varName;
Mutability varMutability;
ExpressionNode range;
CodeBlockStmtNode body;
};
struct nodes::WhileStatement {
SourceLocation location;
Optional<String> label;
Optional<String> unwrappedVar;
ExpressionNode condition;
CodeBlockStmtNode body;
Optional<ElseBranchNode> elseBranch;
};
struct nodes::DoWhileStatement {
SourceLocation location;
Optional<String> label;
ExpressionNode condition;
CodeBlockStmtNode body;
};
struct nodes::InfLoopStatement {
SourceLocation location;
Optional<String> label;
CodeBlockStmtNode body;
};
struct nodes::ExpressionStatement {
SourceLocation location;
ExpressionNode expression;
};
struct nodes::MatchCase {
SourceLocation location;
TypeNode matchType;
Optional<String> unwrappedVar;
CodeBlockStmtNode body;
};
struct nodes::SwitchCase {
SourceLocation location;
ExpressionNode matchExpr;
CodeBlockStmtNode body;
};
} // namespace arti::lang::ast