Skip to content

Replace operator strings with UnaryOp and BinaryOp enums#79

Merged
StreamDemon merged 1 commit into
feature/parser-enhancements-basefrom
feature/parser-enh-operator-enums
Jul 2, 2026
Merged

Replace operator strings with UnaryOp and BinaryOp enums#79
StreamDemon merged 1 commit into
feature/parser-enhancements-basefrom
feature/parser-enh-operator-enums

Conversation

@StreamDemon

@StreamDemon StreamDemon commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces op: String on ExprKind::Unary/Binary with dedicated UnaryOp and BinaryOp enums — no per-node allocation, illegal operators unrepresentable, and exhaustive matching for every consumer.
  • = never reaches the AST (it builds ExprKind::Assign), so the parser classifies infix tokens with a private Infix { Assign, Op(BinaryOp) } wrapper instead of widening the public enum with a variant no AST node can carry.
  • as_str() + Display on both enums recover the source spelling for diagnostics and the future pretty-printer (Parser: pretty-printer + parse→print→parse round-trip validation #67).
  • Tests now assert operators structurally (BinaryOp::Pipe, BinaryOp::Div, ...) instead of comparing strings.

Fourth sub-PR of the enhancement wave (PR #71 roadmap: "Binary/Unary op: String → dedicated operator enums"). No behavior change.

Related Issue

None (PR #71 roadmap item).

Spec Sections Affected

None — implementation quality only. Operator set mirrors the §16 expression grammar and docs/reference/operator-precedence.md exactly (binding powers unchanged).

Checklist

  • Code follows the Sploosh design principles (one way to do it, explicit over implicit, etc.)
  • Documentation updated in relevant docs/ pages — N/A, no behavior change
  • Tests added or updated
  • All build targets still compile (if applicable)
  • Spec-only PR (skip Build Targets section if checked)

Build Targets Tested

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace all green locally.

Test Plan

  • All pipe/precedence/range/assignment tests pass with structural operator assertions (assert_eq!(*op, BinaryOp::Pipe) etc.).
  • Range non-associativity and assignment-target validation keep their existing span-anchored error tests.

Summary by cubic

Replaced operator strings with UnaryOp and BinaryOp enums to remove per-node allocations and make invalid operators unrepresentable. The parser now treats = as assignment (not a binary op), with no behavior changes.

  • Refactors

    • ExprKind::Unary and ExprKind::Binary now store UnaryOp/BinaryOp.
    • Parser uses a private Infix { Assign, Op(BinaryOp) }; = never reaches the AST.
    • Added as_str() and Display on both enums for diagnostics; tests updated to assert enum variants.
  • Migration

    • Replace string comparisons with UnaryOp/BinaryOp matches.
    • Use op.as_str() or format!("{}", op) when you need the operator’s source spelling.

Written for commit bd95672. Summary will update on new commits.

Review in cubic

`ExprKind::Unary`/`Binary` stored their operator as an owned String,
which allocated per node and let any string masquerade as an operator.
Dedicated enums make illegal operators unrepresentable, shrink the
nodes, and give match exhaustiveness checking to every consumer.

`=` never reaches the AST (it builds `ExprKind::Assign`), so the parser
classifies infix tokens with a private `Infix { Assign, Op(BinaryOp) }`
wrapper instead of widening the public enum with a variant no AST node
can carry. `as_str()`/`Display` on both enums recover the source
spelling for diagnostics and the future pretty-printer (#67).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Lexer as Token Stream
    participant Parser as Parser (with Infix enum)
    participant BinaryOp as BinaryOp
    participant UnaryOp as UnaryOp
    participant Expr as Expr AST Node

    Note over Lexer,Expr: Parsing an expression (e.g., a |> b + c)

    Parser->>Lexer: peek token
    Lexer-->>Parser: PipeGt
    Parser->>Parser: infix_binding_power()
    alt TokenKind::PipeGt
        Parser-->>Parser: returns (Infix::Op(BinaryOp::Pipe), 8, 9)
    else TokenKind::Plus
        Parser-->>Parser: returns (Infix::Op(BinaryOp::Add), 9, 10)
    else TokenKind::Eq
        Parser-->>Parser: returns (Infix::Assign, 2, 1)
    else TokenKind::DotDot
        Parser-->>Parser: returns (Infix::Op(BinaryOp::Range), 3, 4)
    end

    Note over Parser: Precedence climbing loop

    Parser->>Parser: consume token
    Parser->>Parser: check Infix variant
    alt Infix::Assign
        Parser->>Expr: build ExprKind::Assign { target, value }
    else Infix::Op(op)
        Parser->>Expr: build ExprKind::Binary { op: BinaryOp::Pipe, left, right }
        Expr-->>Parser: expression node
    end

    Note over Parser: For prefix unary (e.g., *x)

    Parser->>Lexer: peek token
    Lexer-->>Parser: Star
    Parser->>Parser: map token to UnaryOp::Deref
    alt TokenKind::Amp
        Parser->>UnaryOp: UnaryOp::Ref
    else TokenKind::Bang
        Parser->>UnaryOp: UnaryOp::Not
    else TokenKind::Minus
        Parser->>UnaryOp: UnaryOp::Neg
    else TokenKind::Star
        Parser->>UnaryOp: UnaryOp::Deref
    end
    Parser->>Expr: build ExprKind::Unary { op: UnaryOp::Deref, expr }

    Note over Parser,Expr: Range non‑associativity check
    alt last built binary op is Range/RangeInclusive and next token is dotdot/dotdoteq
        Parser->>Parser: error – range cannot nest
    end

    Note over Expr: Test assertions (structural)
    alt test: assert_eq!(*op, BinaryOp::Div)
        Test->>Expr: check kind field
        Expr-->>Test: BinaryOp::Div
    else test: assert_eq!(*op, BinaryOp::Pipe)
        Test->>Expr: check kind field
        Expr-->>Test: BinaryOp::Pipe
    else test: is_assign_target(*op == UnaryOp::Deref)
        Test->>Expr: check Unary node
        Expr-->>Test: UnaryOp::Deref
    end
Loading

Requires human review: Refactoring core AST types and parser logic to replace operator strings with enums. This is a high-impact change that requires human review to ensure correctness.

Re-trigger cubic

@StreamDemon StreamDemon merged commit dea4d82 into feature/parser-enhancements-base Jul 2, 2026
2 checks passed
@StreamDemon StreamDemon deleted the feature/parser-enh-operator-enums branch July 2, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant