Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ jobs:
- uses: actions/checkout@v4
- run: docker compose run --rm test

matrix:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
lua: ["5.1", "5.2", "5.3", "5.4"]
steps:
- uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v10
with:
luaVersion: ${{ matrix.lua }}
- uses: leafo/gh-actions-luarocks@v4
- run: |
luarocks install lpeg
luarocks install dkjson
luarocks install argparse
luarocks install busted
- run: luarocks make ./parse_sdp-1.3.0-1.rockspec
- run: busted spec/

conformance:
runs-on: ubuntu-latest
steps:
Expand Down
52 changes: 43 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,51 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

- _(none yet; 1.3.0 just shipped)_

## [1.3.0] — 2026-05-26

### Added

- **Lua 5.1 and Lua 5.2 support.** Rockspec dependency relaxed from
`lua >= 5.3, < 5.6` to `lua >= 5.1, < 5.6`. No additional rocks
required on any Lua version — Lua 5.3+ uses native bitwise
operators (`&` / `>>` / `<<`); Lua 5.1 and 5.2 use a pure-Lua
arithmetic backend in `parse_sdp.grammar.bitops_compat`. The
backend is selected at `require` time by the dispatcher in
`parse_sdp.grammar.bitops`, so the 5.3+ path is unchanged.
- **CI matrix across Lua 5.1 / 5.2 / 5.3 / 5.4.** The existing
Docker-based Lua 5.5 job continues to mirror local development;
the new matrix job uses `leafo/gh-actions-lua@v10` and runs the
full hermetic suite on each version. Conformance fixtures still
run on the 5.5 image only.

### Changed

- **Bitwise operations isolated behind a dispatcher (`parse_sdp.grammar.bitops`).**
`int_to_ipv4` and `ipv6_add` in `parse_sdp/grammar/addresses.lua` no
longer contain `&` / `>>` syntax — they call `bitops.band` /
`bitops.rshift` via a shim that loads the native-operator backend on
Lua 5.3+ and a pure-Lua arithmetic backend on 5.1/5.2. Behavior is
unchanged on every Lua version the project supports today; this is
the refactor that unblocks the planned Lua 5.1/5.2 support without
compromising the 5.3+ code path. The rockspec floor is still
`lua >= 5.3, < 5.6` in this entry; the next entry will relax it.
- **Bitwise operations isolated behind a dispatcher
(`parse_sdp.grammar.bitops`).** `int_to_ipv4` and `ipv6_add` in
`parse_sdp/grammar/addresses.lua` no longer contain `&` / `>>`
syntax — they call `bitops.band` / `bitops.rshift` via a shim that
loads the native-operator backend on Lua 5.3+ and a pure-Lua
arithmetic backend on 5.1/5.2. Behavior is unchanged on every
Lua version.
- **`spec/cli_spec.lua` `run()` helper captures exit codes via a
subshell-emitted temp file** instead of relying on the 5.2+
three-value return from `io.popen():close()`. Same observable
behavior on every Lua version; the rewrite is required for the
Lua 5.1 entry above.

### Fixed

- `spec/grammar_base_spec.lua` uses `(table.unpack or unpack)` for
Lua 5.1 compatibility (5.1 has `unpack` as a global; 5.2+ has
`table.unpack`).

### Verification

- Full hermetic suite (1208 tests) passes under Lua 5.1, 5.2, 5.3,
5.4 (via hererocks-built local envs) and Lua 5.5 (via the
existing Docker image).

## [1.2.1] — 2026-05-22

Expand Down
14 changes: 10 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Context and conventions for Claude Code working in this repo.

## Project Purpose

`parse_sdp` is a Lua 5.5 + LPEG library that parses, validates, and serializes SDP
`parse_sdp` is a Lua 5.1+ + LPEG library that parses, validates, and serializes SDP
(Session Description Protocol) files. Three validation tiers:
RFC 8866 (generic SDP; obsoletes RFC 4566) → SMPTE ST 2110 → IPMX.

Expand All @@ -21,11 +21,11 @@ Prefer fewer, well-named things over many small helpers.

| Concern | Choice |
| --- | --- |
| Language | Lua 5.5 |
| Language | Lua 5.1+ (tested on 5.1, 5.2, 5.3, 5.4, 5.5) |
| Parsing | LPEG |
| JSON | dkjson (pure Lua, LuaRocks) |
| Tests | busted — `busted spec/` |
| Container | Docker |
| Container | Docker (Lua 5.5) |

## Repository Layout

Expand Down Expand Up @@ -290,7 +290,13 @@ confirmed-against-primary-source findings get fixed.
- **Strict by default.** If RFC 8866 says a field is required, the parser rejects
input that omits it — no silent defaults, no forgiveness.
- **dkjson** is the only external runtime dependency beyond LPEG.
- Lua 5.5: use `local` and `global` declarations explicitly; no implicit globals.
- **Lua-version portability.** The library supports 5.1 through 5.5 in
a single source tree. Don't introduce 5.3+-only syntax (`&` / `>>` /
`<<`, integer division `//`, `string.pack`, `\u{}` escapes, `<const>`
/ `<close>`, `goto`, etc.) outside the `parse_sdp.grammar.bitops_53`
module, which is only loaded on 5.3+. Don't reach for `table.unpack`
in spec code without the `(table.unpack or unpack)` fallback.
CI runs the full hermetic suite on each of 5.1, 5.2, 5.3, 5.4, 5.5.

## Check Taxonomy

Expand Down
6 changes: 5 additions & 1 deletion GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

## Introduction

`parse_sdp` is a Lua 5.3-5.5 library for parsing, validating, and serializing SDP (Session Description Protocol) files used in professional media over IP workflows. It is built with [LPEG](https://www.inf.puc-rio.br/~roberto/lpeg/). Runtime dependencies: LPEG, [dkjson](https://github.com/LuaDist/dkjson), and argparse (CLI only — never loaded by `require("parse_sdp")`).
`parse_sdp` is a Lua 5.1-5.5 library for parsing, validating, and serializing SDP (Session Description Protocol) files used in professional media over IP workflows. It is built with [LPEG](https://www.inf.puc-rio.br/~roberto/lpeg/). Runtime dependencies: LPEG, [dkjson](https://github.com/LuaDist/dkjson), and argparse (CLI only — never loaded by `require("parse_sdp")`). No bit-manipulation rock is required on any Lua version — Lua 5.3+ uses native bitwise operators, and Lua 5.1 / 5.2 use a pure-Lua arithmetic backend bundled with the library.

**Strictness is a primary feature.** The library enforces RFC 8866 (which obsoletes RFC 4566) exactly: required fields must be present, optional fields must appear in the correct position, and values must conform to their specified formats. SDP files that are "mostly valid" but technically non-conformant are rejected with a precise error message. The library will never produce an invalid SDP file.

Expand Down Expand Up @@ -79,6 +79,10 @@ IPMX (IP Media Experience) is an interoperability profile layered on ST 2110. It

## Installation

### Supported Lua versions

Lua 5.1, 5.2, 5.3, 5.4, and 5.5. CI runs the full hermetic test suite on each version. No additional rocks are needed beyond the standard runtime deps; Lua 5.3+ uses native bitwise operators, and Lua 5.1 / 5.2 use a pure-Lua arithmetic backend (`parse_sdp.grammar.bitops_compat`) selected automatically at `require` time.

### LuaRocks

```sh
Expand Down
16 changes: 10 additions & 6 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

| Concern | Choice |
| --- | --- |
| Language | Lua 5.5 |
| Language | Lua 5.1+ (tested on 5.1, 5.2, 5.3, 5.4, 5.5) |
| Parsing | LPEG |
| JSON | dkjson (pure Lua, LuaRocks) |
| Tests | busted |
Expand Down Expand Up @@ -339,11 +339,15 @@ the AMWA conformance fixtures across the matrix.
`addresses.lua` to call the shim. Rockspec still at `lua >= 5.3`.
Suite grew 1197 → 1208 (+11 bitops cases). Verified passing under
Docker Lua 5.5 and under hererocks-built Lua 5.1.
3. [ ] **Compat slice**: relax rockspec to `lua >= 5.1`, register the
three new modules in `build.modules`, fix `table.unpack` in the
one spec file, add the matrix CI job. Bump version to 1.3.0.
Update CHANGELOG / GUIDE / README / CLAUDE per Migration & version
above.
3. [x] **Compat slice**: rockspec floor relaxed to `lua >= 5.1`,
three new modules registered in `build.modules`, `table.unpack`
fixed via `(table.unpack or unpack)`, `spec/cli_spec.lua`'s
`run()` helper rewritten to capture exit codes via a subshell
(the 5.2+ `io.popen():close()` three-value return doesn't exist
on 5.1). Matrix CI job added using `leafo/gh-actions-lua@v10`
across 5.1 / 5.2 / 5.3 / 5.4. Version bumped to 1.3.0. CHANGELOG
/ GUIDE / README / CLAUDE.md updated. All 1208 tests pass locally
under 5.1, 5.2, 5.3, 5.4, and 5.5.
4. [ ] **Release slice**: tag `v1.3.0`, upload rockspec, publish.

## Known Deferred Items
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Tests](https://github.com/andrewstarks/parse_sdp/actions/workflows/test.yml/badge.svg)](https://github.com/andrewstarks/parse_sdp/actions/workflows/test.yml)

A Lua 5.3 - 5.5 library for parsing, validating, and serializing SDP (Session Description Protocol) files, with support for SMPTE ST 2110 and IPMX extensions.
A Lua 5.1 - 5.5 library for parsing, validating, and serializing SDP (Session Description Protocol) files, with support for SMPTE ST 2110 and IPMX extensions.

Built with [LPEG](https://www.inf.puc-rio.br/~roberto/lpeg/) for precise, composable parsing and structured error reporting.

Expand Down Expand Up @@ -53,6 +53,9 @@ luarocks install parse_sdp

`lpeg`, `dkjson`, and `argparse` are installed automatically as dependencies.
`argparse` is only used by the CLI binary — `require("parse_sdp")` never loads it.
No bit-manipulation rock is required on any Lua version: Lua 5.3+ uses
native bitwise operators, and Lua 5.1/5.2 use a pure-Lua arithmetic
implementation bundled with the library.

**Docker:**

Expand Down
27 changes: 16 additions & 11 deletions parse_sdp-1.2.1-1.rockspec → parse_sdp-1.3.0-1.rockspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package = "parse_sdp"
version = "1.2.1-1"
version = "1.3.0-1"

source = {
url = "git+https://github.com/andrewstarks/parse_sdp.git",
tag = "v1.2.1",
tag = "v1.3.0",
}

description = {
Expand Down Expand Up @@ -31,8 +31,10 @@ round-trip, and registry inspection.

-- argparse is only used by the CLI binary at bin/parse_sdp;
-- require("parse_sdp") loads only the library and never pulls argparse in.
-- Lua 5.1/5.2 get a pure-Lua arithmetic bitops backend; no extra rock
-- is needed on any supported version (see parse_sdp/grammar/bitops.lua).
dependencies = {
"lua >= 5.3, < 5.6",
"lua >= 5.1, < 5.6",
"lpeg",
"dkjson",
"argparse",
Expand All @@ -41,14 +43,17 @@ dependencies = {
build = {
type = "builtin",
modules = {
parse_sdp = "parse_sdp/init.lua",
["parse_sdp.errors"] = "parse_sdp/errors.lua",
["parse_sdp.serialize"] = "parse_sdp/serialize.lua",
["parse_sdp.grammar.patterns"] = "parse_sdp/grammar/patterns.lua",
["parse_sdp.grammar.addresses"] = "parse_sdp/grammar/addresses.lua",
["parse_sdp.grammar.base"] = "parse_sdp/grammar/base.lua",
["parse_sdp.grammar.st2110"] = "parse_sdp/grammar/st2110.lua",
["parse_sdp.grammar.ipmx"] = "parse_sdp/grammar/ipmx.lua",
parse_sdp = "parse_sdp/init.lua",
["parse_sdp.errors"] = "parse_sdp/errors.lua",
["parse_sdp.serialize"] = "parse_sdp/serialize.lua",
["parse_sdp.grammar.patterns"] = "parse_sdp/grammar/patterns.lua",
["parse_sdp.grammar.addresses"] = "parse_sdp/grammar/addresses.lua",
["parse_sdp.grammar.bitops"] = "parse_sdp/grammar/bitops.lua",
["parse_sdp.grammar.bitops_53"] = "parse_sdp/grammar/bitops_53.lua",
["parse_sdp.grammar.bitops_compat"] = "parse_sdp/grammar/bitops_compat.lua",
["parse_sdp.grammar.base"] = "parse_sdp/grammar/base.lua",
["parse_sdp.grammar.st2110"] = "parse_sdp/grammar/st2110.lua",
["parse_sdp.grammar.ipmx"] = "parse_sdp/grammar/ipmx.lua",
},
install = {
bin = {
Expand Down
23 changes: 17 additions & 6 deletions spec/cli_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,43 @@ local dkjson = require("dkjson")
-- stdin_text: optional string piped to the process's stdin.
-- Returns stdout (string), stderr (string), exit_code (number).
local function run(args_str, stdin_text)
local tmp_err = os.tmpname()
-- Subshell the CLI and capture its exit code via a temp file. The
-- 3-value return from `handle:close()` was added in Lua 5.2; on 5.1
-- the popen close returns only a status flag, so reading $? back
-- through the filesystem is the portable path.
local tmp_err = os.tmpname()
local tmp_exit = os.tmpname()
local tmp_in

local cmd
local inner
if stdin_text then
tmp_in = os.tmpname()
local f = assert(io.open(tmp_in, "w"))
f:write(stdin_text)
f:close()
cmd = string.format("lua bin/parse_sdp %s < %s 2>%s", args_str, tmp_in, tmp_err)
inner = string.format("lua bin/parse_sdp %s < %s", args_str, tmp_in)
else
cmd = string.format("lua bin/parse_sdp %s 2>%s", args_str, tmp_err)
inner = string.format("lua bin/parse_sdp %s", args_str)
end
local cmd = string.format("(%s; echo $? > %s) 2>%s", inner, tmp_exit, tmp_err)

local handle = io.popen(cmd, "r")
local stdout = handle:read("*a")
local _, _, code = handle:close()
handle:close()

local ef = io.open(tmp_err, "r")
local stderr = ef and ef:read("*a") or ""
if ef then ef:close() end

local xf = io.open(tmp_exit, "r")
local code = xf and tonumber(xf:read("*a")) or 0
if xf then xf:close() end

os.remove(tmp_err)
os.remove(tmp_exit)
if tmp_in then os.remove(tmp_in) end

return stdout, stderr, code or 0
return stdout, stderr, code
end

-- ── validate subcommand ─────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion spec/grammar_base_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ describe("base SDP grammar — dynamic-PT requires rtpmap (Phase 3.B, RFC 8866
"v=0", "o=- 1 1 IN IP4 127.0.0.1", "s=X",
"c=IN IP4 224.0.0.1/127",
"t=0 0",
table.unpack(media_block),
(table.unpack or unpack)(media_block),
})
end

Expand Down