Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- We implement resilient parser, provides user-friendly error messages.
- We call error messages "diagnostics" to avoid confusion with other errors (see `diagnostics/` folder).
- We strive to achieve excellent stability by enforcing invariants in the code:
- `assert!` and `.expect()` for simple cases
- `panic!`, `assert!` or `.expect()` for simple cases
- `invariants.rs` otherwise, to skip the coverage of unreachable code
- We maintain the architecture decision records (ADRs)
- AI agent is responsible for creating new ADR when such decision was made during agentic coding session
Expand Down
23 changes: 18 additions & 5 deletions crates/plotnik-lib/src/parser/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use rowan::{Checkpoint, TextRange};

use super::core::Parser;
use super::invariants::assert_nonempty;

use super::cst::token_sets::{
ALT_RECOVERY, EXPR_FIRST, QUANTIFIERS, SEPARATORS, SEQ_RECOVERY, TREE_RECOVERY,
Expand Down Expand Up @@ -65,7 +64,11 @@ impl Parser<'_> {

self.peek();
let ate_equals = self.eat(SyntaxKind::Equals);
self.assert_equals_eaten(ate_equals);
assert!(
ate_equals,
"parse_def: expected '=' but found {:?} (caller should verify Equals is present)",
self.current()
);

if EXPR_FIRST.contains(self.peek()) {
self.parse_expr();
Expand Down Expand Up @@ -491,7 +494,12 @@ impl Parser<'_> {
}

let closing = self.peek();
self.assert_string_quote_match(closing, open_quote);
assert_eq!(
closing, open_quote,
"bump_string_tokens: expected closing {:?} but found {:?} \
(lexer should only produce quote tokens from complete strings)",
open_quote, closing
);
self.bump();
}

Expand Down Expand Up @@ -612,7 +620,12 @@ impl Parser<'_> {
self.start_node(SyntaxKind::Field);

let kind = self.peek();
self.assert_id_token(kind);
assert_eq!(
kind,
SyntaxKind::Id,
"parse_field: expected Id but found {:?} (caller should verify Id is present)",
kind
);
let span = self.current_span();
let text = token_text(self.source, &self.tokens[self.pos]);
self.bump();
Expand Down Expand Up @@ -865,7 +878,7 @@ fn to_pascal_case(s: &str) -> String {
}

fn capitalize_first(s: &str) -> String {
assert_nonempty(s);
assert!(!s.is_empty(), "capitalize_first: called with empty string");
let mut chars = s.chars();
let c = chars.next().unwrap();
c.to_uppercase().chain(chars).collect()
Expand Down
35 changes: 0 additions & 35 deletions crates/plotnik-lib/src/parser/invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#![cfg_attr(coverage_nightly, coverage(off))]

use super::core::Parser;
use super::cst::SyntaxKind;

impl Parser<'_> {
#[inline]
Expand All @@ -14,38 +13,4 @@ impl Parser<'_> {
);
self.debug_fuel.set(self.debug_fuel.get() - 1);
}

#[inline]
pub(super) fn assert_equals_eaten(&self, ate_equals: bool) {
assert!(
ate_equals,
"parse_def: expected '=' but found {:?} (caller should verify Equals is present)",
self.current()
);
}

#[inline]
pub(super) fn assert_string_quote_match(&self, actual: SyntaxKind, expected: SyntaxKind) {
assert_eq!(
actual, expected,
"bump_string_tokens: expected closing {:?} but found {:?} \
(lexer should only produce quote tokens from complete strings)",
expected, actual
);
}

#[inline]
pub(super) fn assert_id_token(&self, kind: SyntaxKind) {
assert_eq!(
kind,
SyntaxKind::Id,
"parse_field: expected Id but found {:?} (caller should verify Id is present)",
kind
);
}
}

#[inline]
pub(super) fn assert_nonempty(s: &str) {
assert!(!s.is_empty(), "capitalize_first: called with empty string");
}
14 changes: 9 additions & 5 deletions crates/plotnik-lib/src/query/alt_kinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
use rowan::TextRange;

use super::Query;
use super::invariants::{
assert_alt_no_bare_exprs, assert_root_no_bare_exprs, ensure_both_branch_kinds,
};
use super::invariants::ensure_both_branch_kinds;
use crate::diagnostics::DiagnosticKind;
use crate::parser::{AltExpr, AltKind, Branch, Expr};

Expand All @@ -20,13 +18,19 @@ impl Query<'_> {
self.validate_alt_expr(&body);
}

assert_root_no_bare_exprs(&self.ast);
assert!(
self.ast.exprs().next().is_none(),
"alt_kind: unexpected bare Expr in Root (parser should wrap in Def)"
);
}

fn validate_alt_expr(&mut self, expr: &Expr) {
if let Expr::AltExpr(alt) = expr {
self.check_mixed_alternation(alt);
assert_alt_no_bare_exprs(alt);
assert!(
alt.exprs().next().is_none(),
"alt_kind: unexpected bare Expr in Alt (parser should wrap in Branch)"
);
}

for child in expr.children() {
Expand Down
26 changes: 1 addition & 25 deletions crates/plotnik-lib/src/query/invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,7 @@

#![cfg_attr(coverage_nightly, coverage(off))]

use crate::parser::{AltExpr, Branch, Root};

#[inline]
pub fn assert_root_no_bare_exprs(root: &Root) {
assert!(
root.exprs().next().is_none(),
"alt_kind: unexpected bare Expr in Root (parser should wrap in Def)"
);
}

#[inline]
pub fn assert_alt_no_bare_exprs(alt: &AltExpr) {
assert!(
alt.exprs().next().is_none(),
"alt_kind: unexpected bare Expr in Alt (parser should wrap in Branch)"
);
}
use crate::parser::Branch;

#[inline]
pub fn ensure_both_branch_kinds<'a>(
Expand All @@ -33,11 +17,3 @@ pub fn ensure_both_branch_kinds<'a>(
),
}
}

#[inline]
pub fn ensure_ref_has_name<T>(name: Option<T>) -> T {
name.expect(
"shape_cardinalities: Ref without name token \
(parser only creates Ref for PascalCase Id)",
)
}
6 changes: 4 additions & 2 deletions crates/plotnik-lib/src/query/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
//! undefined refs, etc.).

use super::Query;
use super::invariants::ensure_ref_has_name;
use crate::diagnostics::DiagnosticKind;
use crate::parser::{Expr, Ref, SeqExpr};

Expand Down Expand Up @@ -89,7 +88,10 @@ impl Query<'_> {
}

fn ref_cardinality(&mut self, r: &Ref) -> ShapeCardinality {
let name_tok = ensure_ref_has_name(r.name());
let name_tok = r.name().expect(
"shape_cardinalities: Ref without name token \
(parser only creates Ref for PascalCase Id)",
);
let name = name_tok.text();

let Some(body) = self.symbol_table.get(name).cloned() else {
Expand Down