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 fulldatetimewith 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.5resolve as-is even if already past. AbsoluteMM-DDstill 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 anMM-DD, or anmoday was clamped to the month's last day.inferredTitle— the (normalized) title is absent from the suppliedsourceText. Only evaluated whensourceTextis 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-30rolls over viaDateTimenormalization (→03-02).moanchors clamp to month-end instead. inferredTitlenormalization 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
dewdropoutput lines (dewbead) into structured calendar events.