dewbead

Pure-Dart parser for dewbead lines — the single-line output format of the dewdrop schedule-extraction model. Turns a document into structured ParsedEvents. No runtime dependencies.

Design: two stages, NOW isolated

The model never knows the current time, and neither does the parser's stage 1:

  • parse(String document) — pure syntax, never touches NOW. Splits on ;; and returns ({List<DewbeadAst> asts, List<RecordError> errors}).
  • resolve(DewbeadAst, DateTime now, {String? sourceText}) -> ParsedEvent — the only place NOW is used (day/week/month math, carry, year inference, endpoint computation, review flags).
  • parseDewbead(String document, DateTime now, {String? sourceText}) -> ParseResult — runs both, returning { events, errors }.

Parsing never throws: per-record failures come back as RecordError(reason, rawInput) in ParseResult.errors, while the records that parsed are in ParseResult.events.

Grammar (summary)

document   = "N" | record {";;" record}
record     = type datetime ["~" endpoint] ["@" location] ">" title
type       = "E" (event) | "T" (todo)
datetime   = date_part ["/" time_part]    // "/" present => has a time
date_part  = "t" [±N]                      // today + N days
           | "w" [±N] "." weekday          // week ±N, weekday required
           | "mo" [±N] "." dom             // month ±N, day required
           | (yyyy-)mm-dd                  // ISO absolute
time_part  = hh:mm                         // absolute clock
           | "n" {±N(d|h|m)}               // from NOW (t offset 0 only)
endpoint   = "+"… duration | datetime      // "~" is event-only
  • All-day when there is no /time_part.
  • Anchor is the unit: t+1 = 1 day, w+1 = 1 week, mo+1 = 1 month.
  • Week = Monday start (ISO-8601).
  • Endpoints are duration (~+90m) or a full datetime with an explicit date (~t+2/02:00). There is no clock-only inheritance and no midnight-rollover correction — cross-day ranges name their end date.
  • No past-time correction: w.mon/mo.5 resolve as-is even if already past. Absolute MM-DD still infers a year (rolls forward if past).

Usage

import 'package:dewbead/dewbead.dart';

void main() {
  // Fixed `now` here for deterministic output; in your app pass DateTime.now().
  final now = DateTime(2026, 3, 10, 9, 0); // Tuesday, local
  final result = parseDewbead('Et+1/15:00~+1h@회의실>스탠드업;;Tw.fri>보고서', now);

  for (final e in result.events) {
    print('${e.type.name} ${e.title} ${e.start ?? e.end}');
  }
  // event 스탠드업 2026-03-11 15:00:00.000
  // todo 보고서 2026-03-13 00:00:00.000

  for (final err in result.errors) {
    print('parse failed: ${err.reason} on "${err.rawInput}"');
  }
}

Time zones

start/end are naive local DateTimes carrying the wall-clock in NOW's zone. The library never converts to UTC, so toIso8601String() never emits Z. If you need an explicit zone, apply it at your app boundary.

Review flags

resolve attaches code-owned hints to each event:

  • partialInfo — empty title.
  • inferredDate — a year was filled into an MM-DD, or an mo day was clamped to the month's last day.
  • inferredTitle — the (normalized) title is absent from the supplied sourceText. Only evaluated when sourceText is provided; omitting it skips the check, so the flag is never set in that case.

Known limitations

  • No per-month day/leap validation at parse time: 02-30 rolls over via DateTime normalization (→ 03-02). mo anchors clamp to month-end instead.
  • inferredTitle normalization aggressiveness is still being tuned.

See dewbead-v2.md for the full format specification. The legacy v1 format is documented in dewbead-v1.md and is available in the 0.1.0 release.

Libraries

dewbead
Parse dewdrop output lines (dewbead) into structured calendar events.