{"id":14128629,"url":"https://github.com/rocky/elisp-decompile","last_synced_at":"2025-07-21T11:05:49.177Z","repository":{"id":62575097,"uuid":"103273977","full_name":"rocky/elisp-decompile","owner":"rocky","description":"Emacs Lisp Decompiler","archived":false,"fork":false,"pushed_at":"2022-11-30T00:40:55.000Z","size":252,"stargazers_count":30,"open_issues_count":2,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-21T11:40:17.791Z","etag":null,"topics":["bytecode","compiler","debugging-tool","decompile","elisp","emacs-lisp","lap"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rocky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-12T13:25:49.000Z","updated_at":"2025-02-26T13:23:19.000Z","dependencies_parsed_at":"2023-01-22T07:16:58.005Z","dependency_job_id":null,"html_url":"https://github.com/rocky/elisp-decompile","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/rocky/elisp-decompile","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocky%2Felisp-decompile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocky%2Felisp-decompile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocky%2Felisp-decompile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocky%2Felisp-decompile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rocky","download_url":"https://codeload.github.com/rocky/elisp-decompile/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocky%2Felisp-decompile/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266287824,"owners_count":23905461,"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":["bytecode","compiler","debugging-tool","decompile","elisp","emacs-lisp","lap"],"created_at":"2024-08-15T16:01:58.021Z","updated_at":"2025-07-21T11:05:49.160Z","avatar_url":"https://github.com/rocky.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"[![Downloads](https://pepy.tech/badge/lapdecompile)](https://pepy.tech/project/lapdecompile)\n\nA [decompiler](https://en.wikipedia.org/wiki/Decompiler) for Emacs Lisp bytecode... or at least a proof of concept.\n\nThis code uses the [Python\nspark-parser](https://pypi.python.org/pypi/spark_parser/) for its Earley algorithm parser and the code organization.  I have a project that implements the [Earley algorithm in Emacs\nLisp](https://github.com/rocky/elisp-earley). It needs _a lot_ more work though to replace the Python code.\n\nThis is in a very early stage, but with some hand-waving, the code seems like it could cover everything.\n\nA list of the kinds of things we can decompiler are in the [test/lap](https://github.com/rocky/elisp-decompile/tree/master/test/lap) directory. Two of the longer examples are:\n\n* [test-nested-when.el](https://github.com/rocky/elisp-decompile/blob/master/test/lap/test-when-nested.el) which demonstrates detecting forms like `defvar`, and `defconst`, as well as inverting macros like `when` and\n* [my-gcd.el](https://github.com/rocky/elisp-decompile/blob/master/test/lap/fib.el) which is a recursive fib program that really works.\n\nUntil docs are better organized, see\n[Writing Semantic-action Rules](https://github.com/rocky/python-spark/wiki/Writing-Semantic-action-rules)\nand the\nhttps://github.com/rocky/python-uncompyle6/wiki/Deparsing-Paper for a\nmore general overview.\n\nI gave a\n[talk on Emacs bytecode showing this code](http://rocky.github.io/NYC-Emacs-April-2018). Type\n\"s\" on a slide to see the text associated with the slide.\n\nWe are currently working on documenting Elisp bytecode. See https://github.com/rocky/elisp-bytecode .\n\nYou may find yourself consulting the source code: [`emacs/lisp/emacs-lisp/bytecomp.el`](http://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/emacs-lisp/bytecomp.el),\n[`emacs/src/data.c`](http://git.savannah.gnu.org/cgit/emacs.git/tree/src/data.c) and [`emacs/src/bytecode.c`](http://git.savannah.gnu.org/cgit/emacs.git/tree/src/bytecode.c).\n\n# Bugs\n\nYou should think of this like you would, say, google translate to convert between two human languages: sometimes what you get back is perfect, sometimes what you get back is a little stilted but you still get the idea. And sometimes what you get back is just wrong.\n\nHere, the wrong cases generally involve getting control flow correct. We have the underlying high-powered control-flow code to get control-flow graphs and compute dominators and reverse dominators. However we are not making use of this information right now. And doing so requires a lot of serious thought, engineering, and experimentation.\n\n# Using this code\n\nPerhaps (with help, possibly yours) this will all get converted to Emacs Lisp. But for now it's Python since the decompiler code I have is currently in that language.\n\nThe simplest way to install if via Python's `pip` command:\n\n```\npip install lapdecompile\n```\n\nThis installs the last stable release, but right now what is in github is generally more complete.\n\n\n## Set up Python project\n\nThe below is GNU/Linux centric. Adjust to your OS.\n\nThis is a pretty standard Python project so install however you'd do that from source.\n\n\n```\n$ pip install -e .\n```\n\n## Get a LAP file via disassembly\n\nYou need to run code in the `elisp/dedis.el` to produce disassembly\nthat you'd write to a file that ends in `.lap`.  We have a number of\nsample LAP files in `testdata` in case you want to try a canned example.\n\nThe specific functions in `dedis.el` are `disassemble-file` and `disassemble-full`.\n\nSo inside Emacs find a function you want to disassemble and run `M-x disassemble-full`. Or find a bytecode file you want and run or `M-x disassemble-file`\n\nThe first is like `disassemble`, but we don't truncate the output of docstrings or other things that `disassemble` generally does. The second is like `disassemble-full` but we start out with a bytecode file instead of a lisp function.\n\n## Decompile LAP file\n\nAfter you have written the results of the last section to LAP file, the file should end in `.lap`, and have set up the Python code, you can try to disassemble:\n\n```\n$ lapdecompile \u003cname-of-lap-file\u003e [options]\n```\n\nThere is perhaps a *lot* of debug output. There is even some flow control that isn't really used at the moment. You can probably go into the Python and comment this stuff out.\n\nAlso, what we can decompile right now is a bit limited. I see no technical difficulties other than lots of work. So please help out.\n\n### decompiler options\n\nHere is the help usage for `lapdecompile`:\n\n```\n$ python ./lapdecompile/main.py --help\nUsage: main.py [OPTIONS] LAP_FILENAME\n\n  Lisp Assembler Program (LAP) decompiler\n\nOptions:\n  -a, --assembly / --no-assembly  Show LAP assembly\n  -G, --graphs / --no-graphs      Produce dot/png control-flow graphs\n  -g, --grammar / --no-grammar    Show grammar reductions\n  --tree [after|before|full|none]\n                                  Show parse tree\n  -t                              alias for --tree=after\n  -T                              alias for --tree=full\n  --help                          Show this message and exit.\n```\n\nOne of the cool options options `-G` or `--graphs` will produce dot/png control flow and dominator tree graphs.\n\n# But is it worth it?\n\nA number of people have opined that you really don't need a decompiler.\n\n__Aside:__\n\n_Whenever something new or novel comes along, in addition to the group that says \"Why not?!\" there is the \"Why bother?\"\" group.  In my lifetime, I have experienced this attitude when undo and regular expressions were added to GNU Emacs, adding a debugger to languages where debugging support didn't exist or was weak, using decomplation to give more precise error location information, and here. Convincing the crowd that is happy with the status quo is hard, and quite frankly this project isn't mature. The next section is more for those who have an open mind and want to see a better world._\n\nBelow are the arguments offered and why I think they are inadequate.\n\n\n### It's GNU Emacs, so of course I have the source code!\n\nThere is a difference between being able to find the source code and having it accessible when you need it, such as at runtime in a stack trace.\n\nWhen many people hear the word \"decompile\" they think reverse engineering or hacking code where source has deliberately been withheld. But are other situations where a decompiler is useful.\n\nA common case is where you wrote the code, but have accidentally deleted the source code and just have the bytecode file.\n\nBut, I know, you always use version control and GNU Emacs provides it's tilde backup file.\n\nSo that leads us to the situation where there are _several_ possible source-code versions around, e.g. a development version and a stable version, or one of the various versions that in your version-control system, and you'd like to know which one of those corresponds to the bytecode that is stored in a bytecode file, or that you have loaded.\n\n\nAnd then we come to situation where there _is_ no source-code file. One can create functions on the fly and change them on the fly.  Lisp is known for its culture in having programs create programs; it is possible such a program doesn't have a ``debug'' switch (that you know\nabout) to either save or show the result before it was byte-compiled.  Perhaps you'd like to reconstruct the source code for a function that you worked on interactively.\n\n### Isn't it simpler to just disassemble?\n\nInterestingly, a number of people have proffered the suggestion that it might just be easier to understand LAP and disassemble than write this code.\n\nMost people don't know LAP. In fact, before I started writing the [Elisp bytecode reference](https://github.com/rocky/elisp-bytecode), there really _wasn't_ any good documentation on LAP, short of reading source code.\n\nFrom writing this decompiler and noting all of the subtleties and intricacies, I am pretty convinced that those who say they understand LAP have to do *a lot* of time-consuming tedious work to decipher things.\n\nThis is what computers were invented for. They do this stuff very fast compared to humans.\n\nHere are some simple examples:\n\n#### Macros are expanded\n\nI would find it tedious enough just to descramble something that has\nbeen macro expanded. And I am sure people may find that unsatisfying\nright now with our results. Now imagine how you'd feel to add another\nlayer on that in going from LAP to Elisp for the expanded macros.\n\nThe LAP instructions for:\n\n```\n(define-minor-mode testing-minor-mode \"Testing\")\n```\n\nexpand to 60 or so LAP instructions; a lot more when various parameters\nhave been filled in. This decompiler has the ability to show both reconstructed show both before compressing into macros, should you want to see in high-level terms what is going on, and after.\n\nAnd then you have things like `dolist` which are just long and boring template kinds of things. Because it's long it is very easy to lose sight of what it is.\n\n#### Stacked values\n\nKeeping track of values pushed on a stack is also tedious. Again, there can be some non-locality in when a value is pushed with when it is used and popped. As an example, consider this LAP code which is similar to a well-known mathematical function:\n\n```\n        constant  fn\n        dup\n        varref    n\n        sub1\n        call      1\n        constant  fn\n        varref    n\n        constant  2\n        diff\n        call      1\n        plus\n        call      1\n```\n\n\nAt what point is that duplicated function the second instruction used? And what are the arguments to this function?\n\nHow long did it take you to figure this out? It takes a computer hundredths of a second to reconstruct the Lisp code.\n\n#### Keyboard bindings\n\nYet another piece of tedium for the few that know how to do.\n\nSee how fast you can come up with the key names for:\n\n```\n[134217761]\n[134217854]\n[134217820]\n```\n\nAgain this is done in hundredths of a second by a computer.\n\n#### Pedagogy\n\nAnother aspect about a decompiler, especially this one, is that it can help you learn LAP and Emacs bytecode, and how the existing compiler and optimizer work.\n\nThe program creates a parse tree from (abstracted) LAP instructions, where the the higher levels of the tree consist of grammar nodes that should be familiar in higher-level Emacs Lisp terms.\n\nWith this it is possible to see how the individual instructions combine to form the higher-level constructs.\n\nHere is a decompilation using of the LAP instructions listed earlier\n\n```\n$ lap-decompile --tree=after tmp/foo.lap\nfn_body (3)\n  0. body\n    exprs\n      expr_stmt (2)\n        0. expr\n          call_exprn (3)\n            0. expr\n              name_expr\n                    0 CONSTANT   fn\n            1. expr\n              binary_expr (3)\n                0. expr\n                  call_exprn (3)\n                    0. expr\n                          1 DUP\n                    1. expr\n                      unary_expr (2)\n                        0. expr\n                              2 VARREF     n\n                        1. unary_op\n                              3 SUB1\n                    2.    4 CALL_1     1\n                1. expr\n                  call_exprn (3)\n                    0. expr\n                      name_expr\n                            5 CONSTANT   fn\n                    1. expr\n                      binary_expr (3)\n                        0. expr\n                              6 VARREF     n\n                        1. expr\n                          name_expr\n                                7 CONSTANT   2\n                        2. binary_op\n                              8 DIFF\n                    2.    9 CALL_1     1\n                2. binary_op\n                     10 PLUS\n            2.   11 CALL_1     1\n        1. opt_discard\n  1. opt_label\n  2. opt_return\n(defun foo(n)\n  (fn (+ (fn (1- n)) (fn (- n 2)))))\n```\n\nLooking at the above we see that:\n\n```\n1 DUP\n2 VARREF     n\n3 SUB1\n4 CALL_1     1\n```\n\ncontains a function call and its parameters; One of the parameters is the unary expression:\n\n```\n2 VARREF     n\n3 SUB1\n```\n\nAnd if you want to know which operation the stack value of first instruction `CONSTANT fn`\nis used in, the nesting makes it easy to see it is the very last instruction `CALL_1`.\n\nAs I mentioned before, personally, I find matching this stuff up a bit tedious.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frocky%2Felisp-decompile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frocky%2Felisp-decompile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frocky%2Felisp-decompile/lists"}