diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63fe5eb..03dad82 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4e48e..dd8ef3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 7afebd8..11a7eaa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. @@ -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 @@ -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, `` + / ``, `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 diff --git a/GUIDE.md b/GUIDE.md index ada1a10..b2d83ca 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -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. @@ -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 diff --git a/PLAN.md b/PLAN.md index 0490f40..19a10a1 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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 | @@ -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 diff --git a/README.md b/README.md index 365a496..309c8dc 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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:** diff --git a/parse_sdp-1.2.1-1.rockspec b/parse_sdp-1.3.0-1.rockspec similarity index 58% rename from parse_sdp-1.2.1-1.rockspec rename to parse_sdp-1.3.0-1.rockspec index 59e4110..fff232e 100644 --- a/parse_sdp-1.2.1-1.rockspec +++ b/parse_sdp-1.3.0-1.rockspec @@ -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 = { @@ -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", @@ -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 = { diff --git a/spec/cli_spec.lua b/spec/cli_spec.lua index c92c202..04a2268 100644 --- a/spec/cli_spec.lua +++ b/spec/cli_spec.lua @@ -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 ───────────────────────────────────────────────────── diff --git a/spec/grammar_base_spec.lua b/spec/grammar_base_spec.lua index 4a16963..ac8b148 100644 --- a/spec/grammar_base_spec.lua +++ b/spec/grammar_base_spec.lua @@ -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