<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp" -->

---
title: Parsers don’t have to be complicated | daily.dev
description: A walkthrough of bx::Scanner, a small zero-copy, allocation-free C++ scanning library that sits between ad-hoc pointer arithmetic and full parser generators....
canonical: https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Parsers don’t have to be complicated | daily.dev
og:description: A walkthrough of bx::Scanner, a small zero-copy, allocation-free C++ scanning library that sits between ad-hoc pointer arithmetic and full parser generators....
og:url: https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp
og:image: https://api.daily.dev/og/posts/HiriEMPGp.png
og:image:alt: Parsers don’t have to be complicated
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Parsers don’t have to be complicated

**[Hacker News](https://daily.dev/sources/hn)** · 9 min read · 0 upvotes · 0 comments

## Summary

A walkthrough of bx::Scanner, a small zero-copy, allocation-free C++ scanning library that sits between ad-hoc pointer arithmetic and full parser generators. The author explains the design constraints: a single cursor, built-in line/column tracking, character classes, and a tiny public API. Practical examples show how it replaces an INI library, simplifies URL parsing, handles file path normalization, and parses stack trace symbolication output — all without external dependencies or generated code.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://bkaradzic.github.io/posts/scanner>

## Questions this post answers

### How do I track line and column numbers in a hand-written C++ scanner without extra state variables?

bx::Scanner tracks line and column automatically on every cursor movement, including backwards seeks. getLine() and getColumn() are always available and both return one-based values, making error messages nearly free. No manual newline counting or separate state variables are needed — any accept, acceptWhile, or acceptUntil call that crosses a newline updates the counters internally.

_C++ developers building text tools track patterns like these on daily.dev._

### How can I prevent a malformed input line from causing my parser to read past the end of that line?

Construct a sub-scanner scoped to a single line by passing the result of acceptUntil(EndOfLine) as the input to a new Scanner. Any subsequent accept or acceptUntil calls on that inner scanner are bounded to that line's content, making it structurally impossible to run past the line boundary regardless of how malformed the input is.

_Developers hardening parsers against malformed input find practical patterns like this on daily.dev._

### What is the difference between acceptUntil returning empty and a failed match in bx::Scanner?

In bx::Scanner, acceptUntil returns an empty StringView both when the delimiter is not found anywhere in the remaining input and when the delimiter is already sitting at the current cursor position — in both cases the cursor does not move. The empty view still points at the cursor, making it a zero-length slice rather than a null. To distinguish structurally whether a delimiter was present, call accept or peek on the delimiter separately.

_Developers working through scanner edge cases like this find concrete C++ examples on daily.dev._

## Similar posts on daily.dev

- [But what about K?](https://daily.dev/posts/but-what-about-k--efjvokeag) · Lobsters · 0 upvotes · 0 comments
- [Parsing: How Ruby Understands Your Code](https://daily.dev/posts/parsing-how-ruby-understands-your-code-flstsejne) · RUBYLAND · 0 upvotes · 0 comments
- [Let's make a programming language. Parser](https://daily.dev/posts/let-s-make-a-programming-language-parser-ddrko7v9u) · C\+\+ · 0 upvotes · 0 comments

---

Tags: [#c++](https://daily.dev/tags/c++)

[View this post on daily.dev](https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Parsers don’t have to be complicated","url":"https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp"},"datePublished":"2026-08-07T09:50:31.150Z","dateModified":"2026-08-07T09:51:00.516Z","description":"A walkthrough of bx::Scanner, a small zero-copy, allocation-free C++ scanning library that sits between ad-hoc pointer arithmetic and full parser generators....","image":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","thumbnailUrl":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","isAccessibleForFree":true,"articleSection":"Hacker News","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Hacker News","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/hn","url":"https://daily.dev/sources/hn"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"c++","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Hacker News","item":"https://daily.dev/sources/hn"},{"@type":"ListItem","position":3,"name":"Parsers don’t have to be complicated"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/parsers-don-t-have-to-be-complicated-hiriempgp#faq","mainEntity":[{"@type":"Question","name":"How do I track line and column numbers in a hand-written C++ scanner without extra state variables?","acceptedAnswer":{"@type":"Answer","text":"bx::Scanner tracks line and column automatically on every cursor movement, including backwards seeks. getLine() and getColumn() are always available and both return one-based values, making error messages nearly free. No manual newline counting or separate state variables are needed — any accept, acceptWhile, or acceptUntil call that crosses a newline updates the counters internally. C++ developers building text tools track patterns like these on daily.dev."}},{"@type":"Question","name":"How can I prevent a malformed input line from causing my parser to read past the end of that line?","acceptedAnswer":{"@type":"Answer","text":"Construct a sub-scanner scoped to a single line by passing the result of acceptUntil(EndOfLine) as the input to a new Scanner. Any subsequent accept or acceptUntil calls on that inner scanner are bounded to that line's content, making it structurally impossible to run past the line boundary regardless of how malformed the input is. Developers hardening parsers against malformed input find practical patterns like this on daily.dev."}},{"@type":"Question","name":"What is the difference between acceptUntil returning empty and a failed match in bx::Scanner?","acceptedAnswer":{"@type":"Answer","text":"In bx::Scanner, acceptUntil returns an empty StringView both when the delimiter is not found anywhere in the remaining input and when the delimiter is already sitting at the current cursor position — in both cases the cursor does not move. The empty view still points at the cursor, making it a zero-length slice rather than a null. To distinguish structurally whether a delimiter was present, call accept or peek on the delimiter separately. Developers working through scanner edge cases like this find concrete C++ examples on daily.dev."}}]}
```

