{"id":22375862,"url":"https://github.com/stylewarning/dynamic-collect","last_synced_at":"2026-01-06T16:04:59.064Z","repository":{"id":236852839,"uuid":"625691762","full_name":"stylewarning/dynamic-collect","owner":"stylewarning","description":null,"archived":false,"fork":false,"pushed_at":"2023-04-09T22:19:04.000Z","size":439,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T23:34:10.508Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stylewarning.png","metadata":{"files":{"readme":"README","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-09T22:07:31.000Z","updated_at":"2024-09-10T14:05:16.000Z","dependencies_parsed_at":"2024-04-29T00:08:21.700Z","dependency_job_id":"082a55cc-9333-4c23-b3a7-56a069df4351","html_url":"https://github.com/stylewarning/dynamic-collect","commit_stats":null,"previous_names":["stylewarning/dynamic-collect"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stylewarning%2Fdynamic-collect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stylewarning%2Fdynamic-collect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stylewarning%2Fdynamic-collect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stylewarning%2Fdynamic-collect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stylewarning","download_url":"https://codeload.github.com/stylewarning/dynamic-collect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245708990,"owners_count":20659625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-04T21:28:01.184Z","updated_at":"2026-01-06T16:04:58.977Z","avatar_url":"https://github.com/stylewarning.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"                           DYNAMIC-COLLECT\n                           ===============\n\n                           By Robert Smith\n\nDYNAMIC-COLLECT is a library for dynamic, continuable, and abortable\ncollection of data.\n\nThis code is useful for times when data needs to be collected at\nvarious points in a program, but it is inconvenient to modify the\nprogram and create new pipelines for passing data around. It is also\nuseful when such pipelines would take away from the intent of the\nprogram.\n\nThis code was originally created for tasks in code analysis, where\nwarnings and errors were collected during the analysis, and then\nprocessed a posteriori.\n\n\n                               EXAMPLE\n                               -------\n\nFirst, we will define a function which intends to do some kind of\nanalysis of data, and warn if it's not the right type. In this case,\nwe will check that it's an integer. If it's not, we'll warn that we\nwanted an integer. Actually, we will collect a message that represents\nthe warning.\n\n(defun pass-1 (data)\n  (unless (integerp data)\n    (collect (format nil \"Warning: Not an integer: ~S\" data))))\n\nNext, we will do a more stringent analysis. If the data is a non-null\nlist, then we will require that the first element of the list needs to\nbe a symbol. Perhaps this represents a function call. If the data\nindeed doesn't look like a function call, then we will collect an\nerror, and specify that collection (and further analysis) must not\ncontinue, so we pass NIL to CONTINUEP.\n\n(defun pass-2 (data)\n  (when (and (listp data)\n             (plusp (length data)))\n    (unless (symbolp (car data))\n      (collect (format nil \"Error: A function call needs a symbol ~\n                            in the first position, given: ~S\"\n                       (car data))\n               :continuep nil))))\n\nNow a simple function to process our collected messages. In this case,\nwe will just print them out in a friendly fashion.\n\n(defun process-messages (messages)\n  (if (null messages)\n      (format t \"No messages.~%\")\n      (format t \"~@(~R~) message~:P:~%~{  \u003e\u003e ~A~%~}\"\n                (length messages)\n                messages)))\n\nFinally, we have our main entry point to our analysis. We use\nWITH-DYNAMIC-COLLECTION, and do the passes on our data. We will log\nwhen a pass completes to the user.\n\n(defun main (data)\n  (let ((messages (with-dynamic-collection ()\n                    (pass-1 data)\n                    (format t \";;; Done with pass 1.~%\")\n                    (force-output)\n                    (pass-2 data)\n                    (format t \";;; Done with pass 2.~%\")\n                    (force-output))))\n    (process-messages messages)))\n\nNow for some test runs. First, we provide completely \"legitimate\" data\n(according to our passes).\n\nCL-USER\u003e (main 5)\n;;; Done with pass 1.\n;;; Done with pass 2.\nNo messages.\nNIL\n\nAs seen, the passes complete, and we have no messages. Now let's do\nthe analysis on something that warns on the first pass.\n\nCL-USER\u003e (main :quux)\n;;; Done with pass 1.\n;;; Done with pass 2.\nOne message:\n  \u003e\u003e Warning: Not an integer: :QUUX\nNIL\n\nAs seen, both passes complete, but we ended up with a warning we\ncollected. Now let's do something that passes the second analysis, but\nwarns on the first, again.\n\nCL-USER\u003e (main '(hello))\n;;; Done with pass 1.\n;;; Done with pass 2.\nOne message:\n  \u003e\u003e Warning: Not an integer: (HELLO)\nNIL\n\nSame thing. Finally, let's do something that will cause issues with\nboth.\n\nCL-USER\u003e (main '(5 hello))\n;;; Done with pass 1.\nTwo messages:\n  \u003e\u003e Warning: Not an integer: (5 HELLO)\n  \u003e\u003e Error: A function call needs a symbol in the first position, given: 5\nNIL\n\nNote this time that both warnings were collected and displayed. But\nmore importantly, note that we never reached the end of the second\npass; our collection aborted early.\n\nDespite this very contrived example, early termination is very\nuseful. For example, if we are analyzing a file that ends up being\nunable to be parsed, we can collect an error message, and fail early.\n\n\n                         ENSURING CORRECTNESS\n                         --------------------\n\nUsing the variable *ENSURE-HANDLED-COLLECT*, we can error if the\nsystem detects that a COLLECT is used without a properly enclosing\nWITH-DYNAMIC-COLLECTION.\n\nBy default, *ENSURE-HANDLED-COLLECT* is NIL, which means that\nunhandled COLLECT forms will just return their RETURN keyword\nparameter value.\n\n\n                    COMPOSING DYNAMIC COLLECTIONS\n                    -----------------------------\n\nWITH-DYNAMIC-COLLECTION is actually composable via the notion of\ntags. A \"tag\" is an EQL-comparable thing (often a keyword) that lets\none match up a WITH-DYNAMIC-COLLECTION with a COLLECT form. We simply\nprovide tags to each, and collection will be matched up\naccordingly. For example:\n\nCL-USER\u003e (format t \"OUTER: ~S~%\"\n                 (with-dynamic-collection (:tag :outer)\n                   (collect 1 :tag :outer)\n                   (format t \"INNER: ~S~%\"\n                           (with-dynamic-collection (:tag :inner)\n                             (collect 2 :tag :outer)\n                             (collect 3 :tag :inner)))))\n\nwill print\n\nINNER: (3)\nOUTER: (1 2)\n\nas expected. By default, the tags are NIL (an EQL-comparable value),\nwhich is sufficient when there's no composition.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstylewarning%2Fdynamic-collect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstylewarning%2Fdynamic-collect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstylewarning%2Fdynamic-collect/lists"}