or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

character-parsing.mdcombinators.mdcore-primitives.mdindex.mdparser-generation.mdparser-operators.md

parser-operators.mddocs/

0

# Parser Operators

1

2

Monadic and compositional operators for sequencing, choice, transformation, and control flow between parsers. These operators enable both infix notation and functional composition patterns, providing the core building blocks for parser combination.

3

4

## Capabilities

5

6

### Monadic Operations

7

8

Core monadic operations that enable sequential composition and value transformation in parser chains.

9

10

```python { .api }

11

def bind(self, fn):

12

"""

13

Monadic bind operation (>>=).

14

Passes the result of this parser to fn, continues with returned parser.

15

16

Args:

17

fn (function): Function that takes parsed value and returns Parser

18

19

Returns:

20

Parser: Combined parser

21

22

Note:

23

If this parser fails, fn is not called and failure is returned.

24

"""

25

26

def compose(self, other):

27

"""

28

Sequential composition (>>).

29

Runs this parser, then other parser, returns result of other.

30

31

Args:

32

other (Parser): Parser to run after this one

33

34

Returns:

35

Parser: Combined parser returning other's result

36

37

Note:

38

If this parser fails, other is not run.

39

"""

40

41

def parsecmap(self, fn):

42

"""

43

Transform the result of this parser with a function.

44

45

Args:

46

fn (function): Function to transform the parsed value

47

48

Returns:

49

Parser: Parser that returns fn(parsed_value)

50

51

Note:

52

Only applies fn if parsing succeeds.

53

"""

54

```

55

56

### Choice Operations

57

58

Alternative selection between parsers with different backtracking behaviors.

59

60

```python { .api }

61

def choice(self, other):

62

"""

63

Choice without backtrack (|).

64

Try this parser first. If it fails without consuming input, try other.

65

66

Args:

67

other (Parser): Alternative parser to try

68

69

Returns:

70

Parser: Parser that returns result of first successful parser

71

72

Note:

73

No backtracking - if this parser consumes input then fails,

74

other is not tried.

75

"""

76

77

def try_choice(self, other):

78

"""

79

Choice with backtrack (^).

80

Try this parser first. If it fails, try other regardless of input consumption.

81

82

Args:

83

other (Parser): Alternative parser to try

84

85

Returns:

86

Parser: Parser that returns result of first successful parser

87

88

Note:

89

Full backtracking - other is tried even if this parser consumed input.

90

"""

91

```

92

93

### Sequence Operations

94

95

Operations for combining parser results and controlling parser termination.

96

97

```python { .api }

98

def joint(self, other):

99

"""

100

Joint operation (+).

101

Combine results from two sequential parsers.

102

103

Args:

104

other (Parser): Parser to run after this one

105

106

Returns:

107

Parser: Parser that returns combined/concatenated results

108

109

Note:

110

Results are aggregated using Value.aggregate().

111

"""

112

113

def skip(self, other):

114

"""

115

Skip operation (<).

116

Run this parser, then other parser, return this parser's result.

117

Other parser consumes its input.

118

119

Args:

120

other (Parser): Parser to run and consume after this one

121

122

Returns:

123

Parser: Parser that returns this parser's result

124

"""

125

126

def ends_with(self, other):

127

"""

128

Ends with operation (<<).

129

Run this parser, then other parser, return this parser's result.

130

Other parser does not consume input.

131

132

Args:

133

other (Parser): Parser to check but not consume after this one

134

135

Returns:

136

Parser: Parser that returns this parser's result

137

138

Note:

139

other parser must succeed but doesn't advance position.

140

"""

141

```

142

143

### Parser Enhancement

144

145

Operations for adding metadata and error handling to parsers.

146

147

```python { .api }

148

def mark(self):

149

"""

150

Add position information to parser result.

151

152

Returns:

153

Parser: Parser that returns (start_pos, value, end_pos)

154

155

Note:

156

Positions are (line, column) tuples with 0-based indexing.

157

"""

158

159

def desc(self, description):

160

"""

161

Add description for better error messages.

162

163

Args:

164

description (str): Description of what this parser expects

165

166

Returns:

167

Parser: Parser with improved error reporting

168

169

Note:

170

If parser fails, error will show this description.

171

"""

172

```

173

174

### Operator Overloads

175

176

Python operator overloads for convenient infix syntax.

177

178

```python { .api }

179

# Infix operators available on Parser instances:

180

parser1 | parser2 # choice (calls choice())

181

parser1 ^ parser2 # try_choice (calls try_choice())

182

parser1 + parser2 # joint (calls joint())

183

parser1 >> parser2 # compose (calls compose())

184

parser1 >>= fn # bind (calls bind())

185

parser1 << parser2 # ends_with (calls ends_with())

186

parser1 < parser2 # skip (calls skip())

187

```

188

189

## Functional API

190

191

Standalone functions that mirror the Parser methods for functional programming style.

192

193

```python { .api }

194

def bind(p, fn):

195

"""Functional version of Parser.bind()"""

196

197

def compose(pa, pb):

198

"""Functional version of Parser.compose()"""

199

200

def joint(pa, pb):

201

"""Functional version of Parser.joint()"""

202

203

def choice(pa, pb):

204

"""Functional version of Parser.choice()"""

205

206

def try_choice(pa, pb):

207

"""Functional version of Parser.try_choice()"""

208

209

def parsecmap(p, fn):

210

"""

211

Functional version of Parser.parsecmap().

212

213

Note:

214

This function has a bug in the original implementation.

215

It calls p.map(fn) but should call p.parsecmap(fn).

216

"""

217

218

def mark(p):

219

"""Functional version of Parser.mark()"""

220

221

def parse(p, text, index):

222

"""

223

Utility function to parse with a parser.

224

225

Args:

226

p (Parser): Parser to use

227

text (str): Text to parse

228

index (int): Starting position

229

230

Returns:

231

Parsed result

232

233

Note:

234

This function appears to have a bug in the original implementation.

235

It calls p.parse(text, index) but Parser.parse() only takes text parameter.

236

"""

237

```

238

239

## Usage Examples

240

241

### Basic Sequencing

242

243

```python

244

from parsec import string, letter, digit, many

245

246

# Compose - run first parser, then second, return second result

247

parser = string("hello") >> string("world")

248

result = parser.parse("helloworld") # Returns "world"

249

250

# Joint - combine results from both parsers

251

parser = string("hello") + string("world")

252

result = parser.parse("helloworld") # Returns "helloworld"

253

254

# Skip - use first result, but consume second parser's input

255

parser = many(letter()) < string(".")

256

result = parser.parse("hello.") # Returns ['h','e','l','l','o']

257

258

# Ends with - first result, second must match but doesn't consume

259

parser = many(letter()) << string("123")

260

result = parser.parse("hello123more") # Returns ['h','e','l','l','o'], "123more" remains

261

```

262

263

### Choice Operations

264

265

```python

266

from parsec import string, letter

267

268

# Regular choice - no backtrack

269

parser = string("ab") | string("ac")

270

result = parser.parse("ab") # Returns "ab"

271

# parser.parse("ac") would fail because string("ab") consumed 'a' but failed on 'c'

272

273

# Try choice - with backtrack

274

parser = string("ab") ^ string("ac")

275

result = parser.parse("ab") # Returns "ab"

276

result = parser.parse("ac") # Returns "ac" (backtracked after "ab" failed)

277

278

# Multiple choices

279

parser = string("cat") ^ string("car") ^ string("card")

280

result = parser.parse("card") # Returns "card"

281

```

282

283

### Transformation and Binding

284

285

```python

286

from parsec import many1, digit, string

287

288

# Transform results

289

number_parser = many1(digit()).parsecmap(lambda digits: int("".join(digits)))

290

result = number_parser.parse("123") # Returns 123 (integer)

291

292

# Monadic binding for conditional parsing

293

@generate

294

def conditional_parser():

295

op = yield string("+") ^ string("-")

296

num1 = yield number_parser

297

num2 = yield number_parser

298

if op == "+":

299

return num1 + num2

300

else:

301

return num1 - num2

302

303

result = conditional_parser.parse("+123456") # Returns 579

304

305

# Using bind directly

306

def make_repeat_parser(char):

307

return many1(string(char))

308

309

parser = string("a").bind(make_repeat_parser)

310

result = parser.parse("aaaa") # Returns ['a', 'a', 'a', 'a']

311

```

312

313

### Position Marking

314

315

```python

316

from parsec import mark, many1, letter, string

317

318

# Mark parser positions

319

marked_word = mark(many1(letter()))

320

start_pos, word, end_pos = marked_word.parse("hello")

321

# start_pos: (0, 0), word: ['h','e','l','l','o'], end_pos: (0, 5)

322

323

# Mark multiple elements

324

@generate

325

def marked_lines():

326

lines = yield many(mark(many(letter())) < string("\n"))

327

return lines

328

329

result = marked_lines.parse("hello\nworld\n")

330

# Returns [((0,0), ['h','e','l','l','o'], (0,5)), ((1,0), ['w','o','r','l','d'], (1,5))]

331

```

332

333

### Error Descriptions

334

335

```python

336

from parsec import string, letter, many1

337

338

# Add descriptive error messages

339

identifier = many1(letter()).desc("identifier")

340

keyword = string("def").desc("def keyword")

341

342

parser = keyword >> identifier

343

344

try:

345

result = parser.parse("define") # Should be "def", not "define"

346

except ParseError as e:

347

print(e.expected) # "def keyword"

348

349

# Chain descriptions

350

complex_parser = (

351

string("function").desc("function keyword") >>

352

many1(letter()).desc("function name") >>

353

string("(").desc("opening parenthesis")

354

)

355

```

356

357

### Functional Style

358

359

```python

360

from parsec import bind, compose, choice, many1, letter, digit, parsecmap

361

362

# Functional composition

363

def to_int(digits):

364

return int("".join(digits))

365

366

number_parser = parsecmap(many1(digit()), to_int)

367

368

# Functional choice and composition

369

word_or_number = choice(

370

parsecmap(many1(letter()), "".join),

371

number_parser

372

)

373

374

# Functional binding

375

def repeat_char_parser(char):

376

return many1(string(char))

377

378

parser = bind(letter(), repeat_char_parser)

379

```