miller/pkg/dsl/cst
John Kerl 12c96298b9
Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122)
* Add MT_BYTES mlrval type: foundation and disposition tables

First step toward a first-class bytes type in the DSL (#1231).
Adds MT_BYTES (payload []byte, rendered as lowercase hex in all output
formats, JSON-encoded as a hex string), extends every disposition
matrix/vector with the new row/column -- real cells for comparison,
sorting, and dot-concat of bytes with bytes; type-error stubs
elsewhere -- and adds sweep tests asserting no table has nil cells,
since Go zero-fills short array literals when MT_DIM grows.

Bytes values are not yet constructible from the DSL; b"..." literals
and constructor/codec functions follow in subsequent commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add b"..." bytes-literal syntax to the DSL

Adds a bytes_literal token to the grammar (regenerating the PGPG lexer
and parser) and a BytesLiteralNode in the CST which evaluates to an
MT_BYTES mlrval. Escape handling reuses UnbackslashStringLiteral,
which is already byte-oriented: b"\xff" is the single byte 0xff.
Unlike string literals, bytes literals never participate in
regex-capture replacement. A bare identifier b is unaffected.

Part of #1231.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add bytes DSL functions: conversions, codecs, and bytes-aware built-ins

- bytes(x) converts strings to bytes; string(b) reinterprets raw bytes
  as UTF-8 text (the reverse)
- base64_decode now always returns bytes (superseding the interim
  string-or-hex behavior); base64_encode accepts string or bytes
- New hex_encode/hex_decode functions
- is_bytes and asserting_bytes predicates
- md5/sha1/sha256/sha512 accept bytes, hashing the raw payload
- strlen of bytes is the byte count; substr/substr0/substr1 on bytes
  slice by byte position and return bytes

The Cyrillic-LDAP scenario from #1231 now works without exec
workarounds: string(base64_decode($x)) recovers the text, and binary
payloads survive undamaged as bytes.

Closes #1231.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add bytes-type docs and regression cases

Documents the bytes type on the data-types page, regenerates the
function-reference/man-page material, and adds regression coverage:
literal escape forms, operators (concat/compare/slice/sort and
type errors), conversions and codec round-trips, and CSV-to-JSON
output rendering of bytes fields.

Part of #1231.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Reposition MT_BYTES to sort adjacent to MT_STRING in the type enum

MT_BYTES was appended after MT_ABSENT for index stability; move it
right after MT_STRING instead, since that's where it conceptually
belongs and where it already sorts in the cmp disposition matrices.
Mechanically re-derive all ~40 disposition tables in pkg/bifs and
pkg/mlrval accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix windows CI

* fix merge

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:58:44 -04:00
..
assignments.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
ast_types.go Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122) 2026-07-03 11:58:44 -04:00
block_exit.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
blocks.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
builtin_function_manager.go Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122) 2026-07-03 11:58:44 -04:00
builtin_function_manager_json.go Switch to --as-json (#2106) 2026-06-28 17:04:10 -04:00
builtin_functions.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
collections.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
cond.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
doc.go Export library code in pkg/ (#1391) 2023-09-10 17:15:13 -04:00
dump.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
emit1.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
emit_emitp.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
emitf.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
env.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
evaluable.go Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122) 2026-07-03 11:58:44 -04:00
filter.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
for.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
functions.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
hofs.go Experiment with mlr script (#2009) 2026-03-05 20:14:32 -05:00
if.go Convert if/else-if chains to typed switch statements (staticcheck QF1003) (#2112) 2026-06-28 18:27:42 -04:00
keyword_usage.go Shell tab-completion for bash and zsh (#2096) 2026-06-21 12:47:54 -04:00
keyword_usage_json.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
leaves.go Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122) 2026-07-03 11:58:44 -04:00
lvalues.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
print.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
README.md Update performance docs (#1991) 2026-02-22 17:04:35 -05:00
root.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
signature.go Multiple style updates (#1974) 2026-02-16 15:49:21 -05:00
statements.go Next batch of lint fixes (#2108) 2026-06-28 17:36:36 -04:00
subroutines.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
tee.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
types.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
udf.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
uds.go Lint round 5+6: staticcheck and errcheck to zero (#2130) 2026-07-03 11:42:08 -04:00
validate.go Next batch of lint fixes (#2108) 2026-06-28 17:36:36 -04:00
warn.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00
while.go Replace GOCC parser-generator with PGPG (#2015) 2026-03-15 22:28:57 -04:00

See ../dsl/README.md for more information about Miller's use of abstract syntax trees (ASTs) and concrete syntax trees (CSTs) within the Miller put/filter domain-specific language (DSL).

Files

  • types.go is a starting point for seeing datatypes involved in the concrete syntax tree.
    • IExecutable is the interface for executable nodes, such as assignment statements, or statement blocks (if-bodies, etc.).
    • IEvaluable is the interface for evaluable expressions (e.g. right-hand sides of assignment statements).
  • root.go contains the top-level logic for building a CST from an AST at parse time (cstRoot, err := cst.Build(astRoot)), as well as executing the CST on a per-record basis (cstRoot.Execute(runtimeState)). See also the put transformer.

Notes

Go is a strongly typed language, but the AST is polymorphic. This results in if/else or switch statements as an AST is walked.

Also, when we modify code, there can be changes in the BNF grammar not yet reflected in the AST. Likewise, there can be AST changes not yet reflected here. (Example: you are partway through adding a new binary operator to the grammar.)

As a result, throughout the code, there are error checks which may seem redundant but which are in place to make incremental development more pleasant and robust.

During CST build from an AST, one starts from the AST root and walks down through the nodes of the AST. Within a caller method, there is an if/else or switch statement on the AST node type. (Example: is this a leaf node, like the string literal "abcd", int literal 3, field-name $x? Or a binary operator like +, or function call like cos?).

Different builder methods are invoked for leaves, operators, etc. There is also, redundantly, a precondition assertion within each builder method: the leaf-builder method checks to make sure it's given an AST leaf node to build from; the operator-builder method checks to make sure it's given an AST operator node to build from; etc. The caller and the callee are doing the same kinds of check, which might seem redundant. The callees return Go error in case there is something new in the caller which has not yet been implemented in the callee.

This is all done to make development more happy: when you see things like CST build: AST unary operator node unhandled you can check the code here and see what you need to do next to continue development.