summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/db/mork/morkParser.h
blob: 61184ee995d9c19e1cdc0aef5da2dd6d34c2bca7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _MORKPARSER_
#define _MORKPARSER_ 1

#ifndef _MORK_
#  include "mork.h"
#endif

#ifndef _MORKBLOB_
#  include "morkBlob.h"
#endif

#ifndef _MORKSINK_
#  include "morkSink.h"
#endif

#ifndef _MORKYARN_
#  include "morkYarn.h"
#endif

#ifndef _MORKCELL_
#  include "morkCell.h"
#endif

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

/*=============================================================================
 * morkPlace: stream byte position and stream line count
 */

class morkPlace {
 public:
  mork_pos mPlace_Pos;    // byte offset in an input stream
  mork_line mPlace_Line;  // line count in an input stream

  void ClearPlace() {
    mPlace_Pos = 0;
    mPlace_Line = 0;
  }

  void SetPlace(mork_pos inPos, mork_line inLine) {
    mPlace_Pos = inPos;
    mPlace_Line = inLine;
  }

  morkPlace() {
    mPlace_Pos = 0;
    mPlace_Line = 0;
  }

  morkPlace(mork_pos inPos, mork_line inLine) {
    mPlace_Pos = inPos;
    mPlace_Line = inLine;
  }

  morkPlace(const morkPlace& inPlace)
      : mPlace_Pos(inPlace.mPlace_Pos), mPlace_Line(inPlace.mPlace_Line) {}
};

/*=============================================================================
 * morkGlitch: stream place and error comment describing a parsing error
 */

class morkGlitch {
 public:
  morkPlace mGlitch_Place;      // place in stream where problem happened
  const char* mGlitch_Comment;  // null-terminated ASCII C string

  morkGlitch() { mGlitch_Comment = 0; }

  morkGlitch(const morkPlace& inPlace, const char* inComment)
      : mGlitch_Place(inPlace), mGlitch_Comment(inComment) {}
};

/*=============================================================================
 * morkMid: all possible ways needed to express an alias ID in Mork syntax
 */

/*| morkMid: an abstraction of all the variations we might need to support
**| in order to present an ID through the parser interface most cheaply and
**| with minimum transformation away from the original text format.
**|
**|| An ID can have one of four forms:
**| 1) idHex            (mMid_Oid.mOid_Id <- idHex)
**| 2) idHex:^scopeHex  (mMid_Oid.mOid_Id <- idHex, mOid_Scope <- scopeHex)
**| 3) idHex:scopeName  (mMid_Oid.mOid_Id <- idHex, mMid_Buf <- scopeName)
**| 4) columnName       (mMid_Buf <- columnName, for columns in cells only)
**|
**|| Typically, mMid_Oid.mOid_Id will hold a nonzero integer value for
**| an ID, but we might have an optional scope specified by either an integer
**| in hex format, or a string name.  (Note that while the first ID can be
**| scoped variably, any integer ID for a scope is assumed always located in
**| the same scope, so the second ID need not be disambiguated.)
**|
**|| The only time mMid_Oid.mOid_Id is ever zero is when mMid_Buf alone
**| is nonzero, to indicate an explicit string instead of an alias appeared.
**| This case happens to make the representation of columns in cells somewhat
**| easier to represent, since columns can just appear as a string name; and
**| this unifies those interfaces with row and table APIs expecting IDs.
**|
**|| So when the parser passes an instance of morkMid to a subclass, the
**| mMid_Oid.mOid_Id slot should usually be nonzero.  And the other two
**| slots, mMid_Oid.mOid_Scope and mMid_Buf, might both be zero, or at
**| most one of them will be nonzero to indicate an explicit scope; the
**| parser is responsible for ensuring at most one of these is nonzero.
|*/
class morkMid {
 public:
  mdbOid mMid_Oid;          // mOid_Scope is zero when not specified
  const morkBuf* mMid_Buf;  // points to some specific buf subclass

  morkMid() {
    mMid_Oid.mOid_Scope = 0;
    mMid_Oid.mOid_Id = morkId_kMinusOne;
    mMid_Buf = 0;
  }

  void InitMidWithCoil(morkCoil* ioCoil) {
    mMid_Oid.mOid_Scope = 0;
    mMid_Oid.mOid_Id = morkId_kMinusOne;
    mMid_Buf = ioCoil;
  }

  void ClearMid() {
    mMid_Oid.mOid_Scope = 0;
    mMid_Oid.mOid_Id = morkId_kMinusOne;
    mMid_Buf = 0;
  }

  morkMid(const morkMid& other)
      : mMid_Oid(other.mMid_Oid), mMid_Buf(other.mMid_Buf) {}

  mork_bool HasNoId() const  // ID is unspecified?
  {
    return (mMid_Oid.mOid_Id == morkId_kMinusOne);
  }

  mork_bool HasSomeId() const  // ID is specified?
  {
    return (mMid_Oid.mOid_Id != morkId_kMinusOne);
  }
};

/*=============================================================================
 * morkSpan: start and end stream byte position and stream line count
 */

class morkSpan {
 public:
  morkPlace mSpan_Start;
  morkPlace mSpan_End;

 public:         // methods
 public:         // inlines
  morkSpan() {}  // use inline empty constructor for each place

  morkPlace* AsPlace() { return &mSpan_Start; }
  const morkPlace* AsConstPlace() const { return &mSpan_Start; }

  void SetSpan(mork_pos inFromPos, mork_line inFromLine, mork_pos inToPos,
               mork_line inToLine) {
    mSpan_Start.SetPlace(inFromPos, inFromLine);
    mSpan_End.SetPlace(inToPos, inToLine);
  }

  // setting end, useful to terminate a span using current port span end:
  void SetEndWithEnd(const morkSpan& inSpan)  // end <- span.end
  {
    mSpan_End = inSpan.mSpan_End;
  }

  // setting start, useful to initiate a span using current port span end:
  void SetStartWithEnd(const morkSpan& inSpan)  // start <- span.end
  {
    mSpan_Start = inSpan.mSpan_End;
  }

  void ClearSpan() {
    mSpan_Start.mPlace_Pos = 0;
    mSpan_Start.mPlace_Line = 0;
    mSpan_End.mPlace_Pos = 0;
    mSpan_End.mPlace_Line = 0;
  }

  morkSpan(mork_pos inFromPos, mork_line inFromLine, mork_pos inToPos,
           mork_line inToLine)
      : mSpan_Start(inFromPos, inFromLine),
        mSpan_End(inToPos, inToLine) { /* empty implementation */
  }
};

/*=============================================================================
 * morkParser: for parsing Mork text syntax
 */

/* parse at least half 0.5K at once */
#define morkParser_kMinGranularity 512
/* parse at most 64 K at once */
#define morkParser_kMaxGranularity (64 * 1024)

#define morkDerived_kParser /*i*/ 0x5073 /* ascii 'Ps' */
#define morkParser_kTag /*i*/ 0x70417253 /* ascii 'pArS' */

// These are states for the simple parsing virtual machine.  Needless to say,
// these must be distinct, and preferably in a contiguous integer range.
// Don't change these constants without looking at switch statements in code.
#define morkParser_kCellState 0  /* cell is tightest scope */
#define morkParser_kMetaState 1  /* meta is tightest scope */
#define morkParser_kRowState 2   /* row is tightest scope */
#define morkParser_kTableState 3 /* table is tightest scope */
#define morkParser_kDictState 4  /* dict is tightest scope */
#define morkParser_kPortState 5  /* port is tightest scope */

#define morkParser_kStartState 6  /* parsing has not yet begun */
#define morkParser_kDoneState 7   /* parsing is complete */
#define morkParser_kBrokenState 8 /* parsing is to broken to work */

class morkParser /*d*/ : public morkNode {
  // public: // slots inherited from morkNode (meant to inform only)
  // nsIMdbHeap*       mNode_Heap;

  // mork_base      mNode_Base;     // must equal morkBase_kNode
  // mork_derived   mNode_Derived;  // depends on specific node subclass

  // mork_access    mNode_Access;   // kOpen, kClosing, kShut, or kDead
  // mork_usage     mNode_Usage;    // kHeap, kStack, kMember, kGlobal, kNone
  // mork_able      mNode_Mutable;  // can this node be modified?
  // mork_load      mNode_Load;     // is this node clean or dirty?

  // mork_uses      mNode_Uses;     // refcount for strong refs
  // mork_refs      mNode_Refs;     // refcount for strong refs + weak refs

  // ````` ````` ````` `````   ````` ````` ````` `````
 protected:                    // protected morkParser members
  nsIMdbHeap* mParser_Heap;    // refcounted heap used for allocation
  morkStream* mParser_Stream;  // refcounted input stream

  mork_u4 mParser_Tag;                 // must equal morkParser_kTag
  mork_count mParser_MoreGranularity;  // constructor inBytesPerParseSegment

  mork_u4 mParser_State;  // state where parser should resume

  // after finding ends of group transactions, we can re-seek the start:
  mork_pos mParser_GroupContentStartPos;  // start of this group

  morkMid mParser_TableMid;  // table mid if inside a table
  morkMid mParser_RowMid;    // row mid if inside a row
  morkMid mParser_CellMid;   // cell mid if inside a row
  mork_gid mParser_GroupId;  // group ID if inside a group

  mork_bool mParser_InPort;  // called OnNewPort but not OnPortEnd?
  mork_bool mParser_InDict;  // called OnNewDict but not OnDictEnd?
  mork_bool mParser_InCell;  // called OnNewCell but not OnCellEnd?
  mork_bool mParser_InMeta;  // called OnNewMeta but not OnMetaEnd?

  mork_bool mParser_InPortRow;  // called OnNewPortRow but not OnPortRowEnd?
  mork_bool mParser_InRow;      // called OnNewRow but not OnNewRowEnd?
  mork_bool mParser_InTable;    // called OnNewMeta but not OnMetaEnd?
  mork_bool mParser_InGroup;    // called OnNewGroup but not OnGroupEnd?

  mork_change mParser_AtomChange;   // driven by mParser_Change
  mork_change mParser_CellChange;   // driven by mParser_Change
  mork_change mParser_RowChange;    // driven by mParser_Change
  mork_change mParser_TableChange;  // driven by mParser_Change

  mork_change mParser_Change;  // driven by modifier in text
  mork_bool mParser_IsBroken;  // has the parse become broken?
  mork_bool mParser_IsDone;    // has the parse finished?
  mork_bool mParser_DoMore;    // mParser_MoreGranularity not exhausted?

  morkMid mParser_Mid;  // current alias being parsed
  // note that mParser_Mid.mMid_Buf points at mParser_ScopeCoil below:

  // blob coils allocated in mParser_Heap
  morkCoil mParser_ScopeCoil;   // place to accumulate ID scope blobs
  morkCoil mParser_ValueCoil;   // place to accumulate value blobs
  morkCoil mParser_ColumnCoil;  // place to accumulate column blobs
  morkCoil mParser_StringCoil;  // place to accumulate string blobs

  morkSpool mParser_ScopeSpool;   // writes to mParser_ScopeCoil
  morkSpool mParser_ValueSpool;   // writes to mParser_ValueCoil
  morkSpool mParser_ColumnSpool;  // writes to mParser_ColumnCoil
  morkSpool mParser_StringSpool;  // writes to mParser_StringCoil

  // yarns allocated in mParser_Heap
  morkYarn mParser_MidYarn;  // place to receive from MidToYarn()

  // span showing current ongoing file position status:
  morkSpan mParser_PortSpan;  // span of current db port file

  // various spans denoting nested subspaces inside the file's port span:
  morkSpan mParser_GroupSpan;  // span of current transaction group
  morkSpan mParser_DictSpan;
  morkSpan mParser_AliasSpan;
  morkSpan mParser_MetaSpan;
  morkSpan mParser_TableSpan;
  morkSpan mParser_RowSpan;
  morkSpan mParser_CellSpan;
  morkSpan mParser_ColumnSpan;
  morkSpan mParser_SlotSpan;

 private:  // convenience inlines
  mork_pos HerePos() const { return mParser_PortSpan.mSpan_End.mPlace_Pos; }

  void SetHerePos(mork_pos inPos) {
    mParser_PortSpan.mSpan_End.mPlace_Pos = inPos;
  }

  void CountLineBreak() { ++mParser_PortSpan.mSpan_End.mPlace_Line; }

  // { ===== begin morkNode interface =====
 public:  // morkNode virtual methods
  virtual void CloseMorkNode(
      morkEnv* ev) override;  // CloseParser() only if open
  virtual ~morkParser();      // assert that CloseParser() executed earlier

 public:  // morkYarn construction & destruction
  morkParser(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
             morkStream* ioStream,  // the readonly stream for input bytes
             mdb_count inBytesPerParseSegment,  // target for ParseMore()
             nsIMdbHeap* ioSlotHeap);

  void CloseParser(morkEnv* ev);  // called by CloseMorkNode();

 private:  // copying is not allowed
  morkParser(const morkParser& other);
  morkParser& operator=(const morkParser& other);

 public:  // dynamic type identification
  mork_bool IsParser() const {
    return IsNode() && mNode_Derived == morkDerived_kParser;
  }

  // } ===== end morkNode methods =====

 public:  // errors and warnings
  static void UnexpectedEofError(morkEnv* ev);
  static void EofInsteadOfHexError(morkEnv* ev);
  static void ExpectedEqualError(morkEnv* ev);
  static void ExpectedHexDigitError(morkEnv* ev, int c);
  static void NonParserTypeError(morkEnv* ev);
  static void UnexpectedByteInMetaWarning(morkEnv* ev);

 public:  // other type methods
  mork_bool GoodParserTag() const { return mParser_Tag == morkParser_kTag; }
  void NonGoodParserError(morkEnv* ev);
  void NonUsableParserError(morkEnv* ev);
  // call when IsNode() or GoodParserTag() is false

  // ````` ````` ````` `````   ````` ````` ````` `````
 public:  // in virtual morkParser methods, data flow subclass to parser
  virtual void MidToYarn(
      morkEnv* ev,
      const morkMid& inMid,  // typically an alias to concat with strings
      mdbYarn* outYarn) = 0;
  // The parser might ask that some aliases be turned into yarns, so they
  // can be concatenated into longer blobs under some circumstances.  This
  // is an alternative to using a long and complex callback for many parts
  // for a single cell value.

  // ````` ````` ````` `````   ````` ````` ````` `````
 public:  // out virtual morkParser methods, data flow parser to subclass
          // The virtual methods below will be called in a pattern corresponding
  // to the following grammar isomorphic to the Mork grammar.  There should
  // be no exceptions, so subclasses can rely on seeing an appropriate "end"
  // method whenever some "new" method has been seen earlier.  In the event
  // that some error occurs that causes content to be flushed, or sudden early
  // termination of a larger containing entity, we will always call a more
  // enclosed "end" method before we call an "end" method with greater scope.

  // Note the "mp" prefix stands for "Mork Parser":

  // mp:Start     ::= OnNewPort mp:PortItem* OnPortEnd
  // mp:PortItem  ::= mp:Content | mp:Group | OnPortGlitch
  // mp:Group     ::= OnNewGroup mp:GroupItem* mp:GroupEnd
  // mp:GroupItem ::= mp:Content | OnGroupGlitch
  // mp:GroupEnd  ::= OnGroupCommitEnd | OnGroupAbortEnd
  // mp:Content   ::= mp:PortRow | mp:Dict | mp:Table | mp:Row
  // mp:PortRow   ::= OnNewPortRow mp:RowItem* OnPortRowEnd
  // mp:Dict      ::= OnNewDict mp:DictItem* OnDictEnd
  // mp:DictItem  ::= OnAlias | OnAliasGlitch | mp:Meta | OnDictGlitch
  // mp:Table     ::= OnNewTable mp:TableItem* OnTableEnd
  // mp:TableItem ::= mp:Row | mp:MetaTable | OnTableGlitch
  // mp:MetaTable ::= OnNewMeta mp:MetaItem* mp:Row OnMetaEnd
  // mp:Meta      ::= OnNewMeta mp:MetaItem* OnMetaEnd
  // mp:MetaItem  ::= mp:Cell | OnMetaGlitch
  // mp:Row       ::= OnMinusRow? OnNewRow mp:RowItem* OnRowEnd
  // mp:RowItem   ::= mp:Cell | mp:Meta | OnRowGlitch
  // mp:Cell      ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
  // mp:CellItem  ::= mp:Slot | OnCellForm | OnCellGlitch
  // mp:Slot      ::= OnValue | OnValueMid | OnRowMid | OnTableMid

  // Note that in interfaces below, mork_change parameters kAdd and kNil
  // both mean about the same thing by default.  Only kCut is interesting,
  // because this usually means to remove members instead of adding them.

  virtual void OnNewPort(morkEnv* ev, const morkPlace& inPlace) = 0;
  virtual void OnPortGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnPortEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnNewGroup(morkEnv* ev, const morkPlace& inPlace,
                          mork_gid inGid) = 0;
  virtual void OnGroupGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnGroupCommitEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
  virtual void OnGroupAbortEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnNewPortRow(morkEnv* ev, const morkPlace& inPlace,
                            const morkMid& inMid, mork_change inChange) = 0;
  virtual void OnPortRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnPortRowEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnNewTable(morkEnv* ev, const morkPlace& inPlace,
                          const morkMid& inMid, mork_bool inCutAllRows) = 0;
  virtual void OnTableGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnTableEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnNewMeta(morkEnv* ev, const morkPlace& inPlace) = 0;
  virtual void OnMetaGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnMetaEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnMinusRow(morkEnv* ev) = 0;
  virtual void OnNewRow(morkEnv* ev, const morkPlace& inPlace,
                        const morkMid& inMid, mork_bool inCutAllCols) = 0;
  virtual void OnRowPos(morkEnv* ev, mork_pos inRowPos) = 0;
  virtual void OnRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnRowEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnNewDict(morkEnv* ev, const morkPlace& inPlace) = 0;
  virtual void OnDictGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnDictEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnAlias(morkEnv* ev, const morkSpan& inSpan,
                       const morkMid& inMid) = 0;

  virtual void OnAliasGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;

  virtual void OnMinusCell(morkEnv* ev) = 0;
  virtual void OnNewCell(morkEnv* ev, const morkPlace& inPlace,
                         const morkMid* inMid, const morkBuf* inBuf) = 0;
  // Exactly one of inMid and inBuf is nil, and the other is non-nil.
  // When hex ID syntax is used for a column, then inMid is not nil, and
  // when a naked string names a column, then inBuf is not nil.

  virtual void OnCellGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
  virtual void OnCellForm(morkEnv* ev, mork_cscode inCharsetFormat) = 0;
  virtual void OnCellEnd(morkEnv* ev, const morkSpan& inSpan) = 0;

  virtual void OnValue(morkEnv* ev, const morkSpan& inSpan,
                       const morkBuf& inBuf) = 0;

  virtual void OnValueMid(morkEnv* ev, const morkSpan& inSpan,
                          const morkMid& inMid) = 0;

  virtual void OnRowMid(morkEnv* ev, const morkSpan& inSpan,
                        const morkMid& inMid) = 0;

  virtual void OnTableMid(morkEnv* ev, const morkSpan& inSpan,
                          const morkMid& inMid) = 0;

  // ````` ````` ````` `````   ````` ````` ````` `````
 protected:                      // protected parser helper methods
  void ParseChunk(morkEnv* ev);  // find parse continuation and resume

  void StartParse(morkEnv* ev);  // prepare for parsing
  void StopParse(morkEnv* ev);   // terminate parsing & call needed methods

  int NextChar(morkEnv* ev);  // next non-white content

  void OnCellState(morkEnv* ev);
  void OnMetaState(morkEnv* ev);
  void OnRowState(morkEnv* ev);
  void OnTableState(morkEnv* ev);
  void OnDictState(morkEnv* ev);
  void OnPortState(morkEnv* ev);
  void OnStartState(morkEnv* ev);

  void ReadCell(morkEnv* ev);
  void ReadRow(morkEnv* ev, int c);
  void ReadRowPos(morkEnv* ev);
  void ReadTable(morkEnv* ev);
  void ReadTableMeta(morkEnv* ev);
  void ReadDict(morkEnv* ev);
  mork_bool ReadContent(morkEnv* ev, mork_bool inInsideGroup);
  void ReadGroup(morkEnv* ev);
  mork_bool ReadEndGroupId(morkEnv* ev);
  mork_bool ReadAt(morkEnv* ev, mork_bool inInsideGroup);
  mork_bool FindGroupEnd(morkEnv* ev);
  void ReadMeta(morkEnv* ev, int inEndMeta);
  void ReadAlias(morkEnv* ev);
  mork_id ReadHex(morkEnv* ev, int* outNextChar);
  morkBuf* ReadValue(morkEnv* ev);
  morkBuf* ReadName(morkEnv* ev, int c);
  mork_bool ReadMid(morkEnv* ev, morkMid* outMid);
  void ReadDictForm(morkEnv* ev);
  void ReadCellForm(morkEnv* ev, int c);

  mork_bool MatchPattern(morkEnv* ev, const char* inPattern);

  void EndSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan);
  void EndSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan);
  void StartSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan);

  void StartSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan);

  // void EndSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan)
  // { MORK_USED_2(ev,ioSpan); }

  // void EndSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan)
  // { MORK_USED_2(ev,ioSpan); }

  // void StartSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan)
  // { MORK_USED_2(ev,ioSpan); }

  // void StartSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan)
  // { MORK_USED_2(ev,ioSpan); }

  int eat_line_break(morkEnv* ev, int inLast);
  int eat_line_continue(morkEnv* ev);  // last char was '\\'
  int eat_comment(morkEnv* ev);        // last char was '/'

  // ````` ````` ````` `````   ````` ````` ````` `````
 public:                    // public non-poly morkParser methods
  mdb_count ParseMore(      // return count of bytes consumed now
      morkEnv* ev,          // context
      mork_pos* outPos,     // current byte pos in the stream afterwards
      mork_bool* outDone,   // is parsing finished?
      mork_bool* outBroken  // is parsing irreparably dead and broken?
  );

 public:  // typesafe refcounting inlines calling inherited morkNode methods
  static void SlotWeakParser(morkParser* me, morkEnv* ev, morkParser** ioSlot) {
    morkNode::SlotWeakNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }

  static void SlotStrongParser(morkParser* me, morkEnv* ev,
                               morkParser** ioSlot) {
    morkNode::SlotStrongNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }
};

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#endif /* _MORKPARSER_ */