docs/slash_grammar.md

# Slash Command Grammar DSL

The `slash/2` DSL is built to make slash commands deterministic, easy to maintain, and fast. Instead of manually splitting strings or juggling regexes, you describe the format you expect and SlackBot generates a parser at compile time. Write your grammar by chaining primitives (`literal/2`, `value/2`, `optional/1`, etc.) and always finish with a single `handle/3` clause—nothing may appear after `handle/3`, and the compiler raises if you try. This guide teaches the DSL in layers so you can follow along as the commands grow in complexity.

---

## Why use the DSL?

- **Deterministic parsing** – handlers receive structured maps, not ad-hoc token lists.
- **Readable expectations** – the command format lives next to the handler, making code
  reviews and maintenance straightforward.
- **Compile-time validation** – malformed definitions fail fast, before your bot ships.
- **Battle-tested parsing** – handles quoting, whitespace, and tricky edge cases without
  extra work on your part.

---

## 1. Literal-only commands

Great for “one-shot” commands that trigger behavior without arguments.

```elixir
slash "/cmd" do
  literal "project"
  literal "report"

  handle payload, ctx do
    # payload["parsed"] => %{command: "cmd"}
    Reports.generate(ctx)
  end
end
```

**Slack input:** `/cmd project report`

---

## 2. Capturing values

Use `value/1` to bind user-provided tokens to names that show up in the parsed payload.

```elixir
slash "/cmd" do
  literal "team", as: :mode, value: :team_show
  value :team_name
  literal "show"

  handle payload, ctx do
    %{team_name: name} = payload["parsed"]
    Teams.show(name, ctx)
  end
end
```

**Slack input:** `/cmd team marketing show`  
**Parsed payload:** `%{command: "cmd", mode: :team_show, team_name: "marketing"}`

---

## 3. Optional segments

Wrap anything that isn’t required in `optional`. Omitted segments simply don’t appear in
the parsed map.

```elixir
slash "/cmd" do
  literal "list", as: :mode, value: :list
  optional literal("short", as: :short?)
  value :app

  handle payload, _ctx do
    payload["parsed"]
  end
end
```

**Slack input:** `/cmd list short foo`  
**Parsed payload:** `%{command: "cmd", mode: :list, short?: true, app: "foo"}`

---

## 4. Repeating segments

`repeat` lets you express “zero or more” patterns. Each `value` inside becomes a list.

```elixir
slash "/cmd" do
  literal "report", as: :mode, value: :report_teams

  repeat do
    literal "team"
    value :teams
  end

  handle payload, _ctx do
    payload["parsed"]
  end
end
```

**Slack input:** `/cmd report team alpha team beta team gamma`  
**Parsed payload:** `%{teams: ["alpha", "beta", "gamma"], mode: :report_teams}`

---

## 5. Branching with `choice`

Many commands act like subcommands. `choice` lets you express each branch declaratively.

```elixir
slash "/cmd" do
  choice do
    sequence do
      literal "list", as: :mode, value: :list
      optional literal("short", as: :short?)
      value :app
    end

    sequence do
      literal "project", as: :mode, value: :project_report
      literal "report"
    end
  end

  handle payload, ctx do
    parsed = payload["parsed"]
    handle_mode(parsed.mode, parsed, ctx)
  end
end
```

**Slack inputs covered:** `/cmd list app`, `/cmd list short app`, `/cmd project report`

---

## 6. End-to-end example

The tests (`test/slack_bot/router_test.exs`) contain a full “GrammarRouter” that combines
all the primitives. Here’s how a few Slack inputs map to payloads:

| Slack input | Parsed payload |
| --- | --- |
| `/cmd list short app param one param two` | `%{mode: :list, short?: true, app: "app", params: ["one","two"]}` |
| `/cmd project report` | `%{mode: :project_report}` |
| `/cmd team marketing show` | `%{mode: :team_show, team_name: "marketing"}` |
| `/cmd report team one team two team three` | `%{mode: :report_teams, teams: ["one","two","three"]}` |

Each branch is explicit, and the handler simply reacts to structured data.

---

## Handler payload structure

Every DSL handler receives an enriched payload under `payload["parsed"]`:

```elixir
%{
  command: "cmd",
  mode: :list,
  short?: true,
  app: "foo",
  params: ["one", "two"],
  teams: ["alpha", "beta"],
  extra_args: ["leftover"] # present only if tokens remain unmatched
}
```

- Repeated values become lists.
- Optional literals store the `value:` option (default `true`) when matched.
- Any leftover tokens land in `:extra_args`, allowing custom fallbacks.

---

## Quick reference

| Macro | Purpose | Example |
| --- | --- | --- |
| `literal value, opts \\ []` | Match a literal token, optionally tagging metadata | `literal "list", as: :mode, value: :list` |
| `value name, opts \\ []` | Capture a token and assign it to `name` | `value :service` |
| `optional do ... end` | Optional group; skipped segments leave previous values untouched | `optional literal("short", as: :short?)` |
| `repeat do ... end` | Repeat group until it no longer matches | `repeat do literal "team"; value :teams end` |
| `choice do ... end` | First matching branch wins | `choice do sequence ... end` |
| `sequence do ... end` | Explicit grouping (helpful inside `choice`) | `sequence do literal "project"; literal "report" end` |
| `handle payload, ctx do ... end` | Handler that receives the enriched payload | `handle payload, ctx do ... end` |

---

## Tips

- Use `SlackBot.Diagnostics.list/2` + `replay/2` to capture real commands and verify they parse as expected.
- Prefer small, focused `choice` branches over one giant handler with nested `case`.
- Need raw tokens? Call `SlackBot.Command.lex/1` yourself.
- See `test/slack_bot/router_test.exs` for more real-world examples.

---

## Next Steps

- [Getting Started](getting_started.md) — set up a Slack App and run your first handler
- [Rate Limiting](rate_limiting.md) — understand how SlackBot paces Web API calls
- [Diagnostics](diagnostics.md) — capture and replay commands for debugging
- [Telemetry Dashboard](telemetry_dashboard.md) — monitor handler execution and timing