Skip to content

Initial Common Lisp implementation of sqids - #1

Open
DavidAlphaFox wants to merge 11 commits into
sqids:mainfrom
TTalkPro:main
Open

Initial Common Lisp implementation of sqids#1
DavidAlphaFox wants to merge 11 commits into
sqids:mainfrom
TTalkPro:main

Conversation

@DavidAlphaFox

Copy link
Copy Markdown
Collaborator

Initial Common Lisp implementation of sqids

Summary

Initial Common Lisp implementation of sqids, ported 1:1 from the official spec.

Highlights

  • Full sqids-spec compliance — output is byte-identical to sqids-python / sqids-js / sqids-go
  • CLOS-based immutable sqids class with validated constructor
  • Follows the Google Common Lisp Style Guide
  • Works on SBCL and CCL with no implementation-specific code
  • 281 FiveAM checks ported 1:1 from sqids-python's official test vectors
  • Macros (with-peeled-separator) and shared helpers eliminate duplication
  • Bilingual documentation (Chinese + English) with cross-links

Spec compliance

All 281 checks pass on both supported implementations:

sbcl --eval '(asdf:test-system :cl-sqids)'   # Did 281 checks. Pass: 281 (100%)
ccl  --eval '(asdf:test-system :cl-sqids)'   # Did 281 checks. Pass: 281 (100%)

Spec-pinned vectors verified:

  • (encode '(1 2 3))"86Rf07"
  • (encode '(0))"bM"
  • (encode '(4572721))"JExTR" (blocklist reshuffle)
  • Full min-length cascade (N=6..65) matches the spec-pinned vectors exactly
  • Cross-language interop confirmed with sqids-python default configuration

License

This submission is distributed under the Apache License, Version 2.0.

The parent repo sqids/sqids-common-lisp is currently MIT-licensed, so I want to
flag this explicitly before merge. Apache 2.0 is BSD/MIT-compatible and is a
strict superset in terms of protections (notably the explicit patent grant and
the anti-aggression clause).

If the sqids org prefers to keep all ports uniformly MIT, I'm open to discussing
relicensing before or after merge. As-is, this submission is offered under
Apache 2.0.

Commit history

This PR was pushed with --force-with-lease because the fork's initial LICENSE
commit (inherited from the parent repo) and this project's history share no
common ancestor. The 6 commits in this PR are atomic and reviewable individually:

  1. Refactor src/ with CLOS, fix 8 spec bugs, support SBCL and CCL
  2. Add FiveAM test suite mirroring official sqids-python vectors
  3. Rewrite README as Chinese-primary with English supplement
  4. Add USAGE.md hands-on guide with examples and FAQ
  5. Add TASK.md refactor planning document
  6. Split bilingual docs into per-language files with cross-links

Happy to squash, restructure, or address any review feedback.

DavidAlphaFox and others added 11 commits June 27, 2025 10:00
Replace plain-function core with a CLOS SQIDS class whose slots are read-only via :reader (no more manual setf override). The constructor shuffles the alphabet exactly once and pre-filters the blocklist into 3 tiers (exact / ends-with / anywhere), matching the official sqids-spec.

Fixes 8 algorithm bugs against sqids-spec:
- B1: TO-ID now uses do/while so (encode '(0)) yields a single char.
- B2: empty list returns "".
- B3: DECODE of out-of-alphabet chars returns NIL instead of erroring.
- B4: alphabet is shuffled once in the constructor (was: per-call).
- B5: blocklist retry loop actually produces a different ID each round.
- B6: tidy NIL / empty-string edge case in encode retry.
- B7: blocklist matching is now the official 3-tier heuristic (was: string=).
- B8: alphabet and min-length are validated at construction.

Other changes:
- conditions.lisp: SQIDS-ERROR base + typed sub-conditions, each with :REPORT.
- constants.lisp: switch compound values to DEFPARAMETER (*name*) to avoid
  SBCL's 'redefining a constant' warning and CCL quirks; scalars stay DEFCONST.
- blocklist.lisp: new module extracting 3-tier matching from the encoder.
- codec.lisp: renamed+rewritten core.lisp; WITH-PEELED-SEPARATOR macro
  eliminates the duplicated separator/suffix binding.
- sqids.lisp: renamed+rewritten cl-sqids.lisp with CLOS class + defgenerics.
- package.lisp: exports condition readers, all conditions, introspection readers.
- cl-sqids.asd: (in-package :asdf-user), add :cl-sqids/tests subsystem with test-op.

Verified spec-correct on SBCL 2.6.6 and CCL 1.13: (encode '(1 2 3)) => 86Rf07,
(encode '(0)) => bM, (encode '(4572721)) => JExTR (blocklist reshuffle),
full min-length cascade matches the spec-pinned vectors.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Five test files plus packages.lisp/suite.lisp form a complete loadable :cl-sqids/tests system.

Test vectors are ported 1:1 from sqids-python's official suite:
- test-encoding.lisp: [1,2,3]=>86Rf07, [0]=>bM, [1]=>Uk, two-number tuples, empty/invalid decode.
- test-minlength.lisp: 8-step + 4-step min-length cascade pinned to spec.
- test-blocklist.lisp: 3-tier matching, [4572721]=>JExTR reshuffle, decoder accepts blocked IDs.
- test-alphabet.lisp: hex alphabet pinned vector, validation errors (too short/duplicate/multibyte).
- test-roundtrip.lisp: round-trip for 0..99, large fixnums, custom alphabet, idempotency.

Run with: (asdf:test-system :cl-sqids).
Result on SBCL 2.6.6 and CCL 1.13: Did 281 checks. Pass: 281 (100%).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Adds installation, quick-start, API reference, customization, error conditions, test runner, compatibility, and project structure sections. Chinese is the primary language; an English section follows at the bottom.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tutorial document complementing README: real-world scenarios (DB primary keys, compound routes, invite codes), customization deep-dive, error handling patterns, cross-language interop with sqids-python/js/go, performance and memory notes, best practices, and a 10-item FAQ. Chinese primary, English quick-reference at the end.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Developer-facing record of the refactor: current-state analysis, 8 algorithm bugs verified against sqids-spec, file-level task breakdown, acceptance criteria, and execution order. Chinese primary with English summary.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Previously README.md and USAGE.md each kept an English section at the bottom. That made the docs long, broke GitHub's language detection, and was awkward to maintain.

Now each language has its own file, with a cross-link at the top of every document:
  README.md    (中文) ↔ README.en.md (English)
  USAGE.md     (中文) ↔ USAGE.en.md  (English)

The English versions are full mirrors of the Chinese structure (not abbreviated
appendices). All four files open with a '中文 · English' line that links to the
other language.

The Chinese project-structure section also now lists all four documentation files.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant