Skip to content

Latest commit

 

History

History
950 lines (630 loc) · 32.4 KB

notes.org

File metadata and controls

950 lines (630 loc) · 32.4 KB

Notes

Roadmap

  1. Improve the reader, add tests
  2. Make commands more declarative (see pattern.lisp)
  3. Improve the documentation
  4. Implement better refactor commands
  5. Improve breeze-eval function, add tests
  6. Reduce breeze’s dependencies
  7. Add support for other editors

Tasks

**Regularly review the tasks** it helps to see the big picture and prioritize accordingly.

fix integration with sly

I’ve been using slime only for a long time… Today I tried sly and it crashed when I tried to use quickfix.

Here’s the problem:

(EVAL (SLYNK:EVAL-AND-GRAB-OUTPUT "(breeze.command:continue-command 1 (1 bit, #x1, #o1, #b1))"))

Which comes from breeze.el’s breeze-interactive-eval function, which calls slynk:interactive-eval.

With slime:

(breeze-interactive-eval "1")
;; => "1"

With sly:

(breeze-interactive-eval "1")
;; => "1 (1 bit, #x1, #o1, #b1)"

Make it easy to report a bugs

  • OS version
  • lisp version
  • editor version
  • quicklisp
    • client version
    • distributions
  • for each dependency
    • from which distribution the system come from
    • version of the system

Make sure the commands are executed against common lisp code

Gavinok tried breeze, but ran C-. on breeze.el, this causes confusion for everybody.

Here’s some ideas to help with this:

  • check the extension of the buffer or file
  • check the mode of the buffer
  • make sure it’s not in the repl (for now)

Use with-branching to define optimal-string-alignment-distance

Instead of having 2 definitions

https://github.com/phoe/with-branching

Use messages in quickfix

e.g. “There is no applicable quickfixes in the current context”

docstrings are not kept in (unparse (parse …))

Basic commands should send the buffer to the editor

e.g. If the common-lisp side ask the editor to insert a string, but the user changed buffer, the editor will insert the string in the wrong buffer.

OR the “client” should be responsible to always execute the commands with on the “initial” buffer, unless stated otherwise (but that would add even more state on the protocol).

Remove the command backward-char

I introduced the command backward-char as a hacky workaround: the integration with the editor edits the buffer incrementally, but then some other editor configuration would interfere with that process (for example aggressive-indent-mode in emacs.

I would prefer to improve the whole integration with the editor than using “backward-chard” as a crutch.

Something like relative-move would be better.

Showcase/demo

Show the integration with quickproject

Show how breeze can help create a project from a file

e.g. you made a “capture” with breeze, and now you want to turn that into a project.

Create a demo to show how to install breeze

Add command “go to next missing documentation”

Add command “got to next documentation”

To help revision

Add command “next todo”

Don’t re-implement it, just figure out which how to configure the editor to do that.

Use header-line in emacs to show the test results

Instead of a lighter

The lighter could still be used though, it could be a ✓ or χ.

Better explain what Breeze currently is in the readme

As opposed to explain the principles, goals, non-goals, etc.

Actually explain what breeze does.

add a “What is this?” section

[#A] On first setup, the user has to (ql:quickload ‘breeze)

Start by documenting how to start using breeze, then automate it.

N.B. Since I remove the test-runner and etc. breeze doesn’t really need to be “started”. But it still needs to be loaded.

Add links to all the “TODOs” in the documentation

grep TODO -h | to_github_url | to_html

It’s too easy to kill the test-runner

Breeze shouldn’t suggest symbol that are “too far”

e.g not suggest “slot-exists-p” when trying to eval “this-really-doesnt-exists”.

Still need to add tests on this…

Make a “string distance” function that stops after a threshold

Use a heap to find the N closest matches

  • Maybe look into VP-trees (Vantage-Point trees)

How would that help the user? Adding a restart for each candidate would really pollute the list of restarts. Perhaps we could keep the current restart, and add another one that shows more candidate (restarts).

Maybe we can refine that list of candidate based on other, perhaps heavier criteria?

Document how to use breeze:defun

When redefining a function defined with breeze:defun, it run tests

When defining a test with breeze:deftest, it run tests

Demonstrate that the test-runner is debounced

When you redefine many function (e.g. when reloading a file), it doesn’t run all the tests each time a something is redefined.

Document (with screenshots) how to setup and use emacs integration

Document how to use quickproject integration

Add binding to run test at point

Add command to choose a test and run it

[#B] When inserting a package definition in an empty buffer, evaluate the buffer

Generate the documentation in the CI

Really not a priority, even though generating the documentation locally and committing the result is less than ideal, it works well.

The main problem is that, AFAIK, you can’t host something in github pages without committing it into a repository. Which means that you have to hack a CI pipeline that commit its results back into the repository. This sucks IMO.

An alternative would be to use GitLab pages, which are way more sane as they allow (require, really) that your pages’ content be generated from the CI pipeline (from a job called “pages” to be exact). On the other hand (again), I’ve had really janky load time with GitLab, but that might just have been the Authentication + boatload of js.

[#C] Add a link to the GitHub repository in the documentation

Add integration tests

Look into emacs-director

https://github.com/bard/emacs-director

Look into makem.sh

makem.sh - Makefile-like script for building and testing Emacs Lisp packages

Try to detect when the current buffer/file was not loaded (evaluated).

The goal would be to warn the user “hey, you’re trying to evaluate that function, but the package declared in this file/buffer doesn’t exists”.

Could pass the file to br:next. What if

  • it’s a buffer that’s not visiting a file?
  • it’s not a buffer in lisp-mode
  • the buffer is empty
  • the buffer requires some reader-macro
  • the buffer is visiting a file, but there are unsaved modifications

Try to suggest new and old projects?

When the user just initialized breeze, try to find out if the user has any lisp project(s) already opened, help him work on it.

If not projects are found guide him through breeze-scaffold

Suggest corrections when typos are detected

We already suggest stuff when there’s, for example, an undefined-function error. We could go one step further and suggest a quickfix in the editor. We should probably suggest that quickfix only when the edit-distance is not too great, or we would get some wild quickfixes.

Maybe add this document (notes.org) to the documentation?

Maybe split the documentation in multiple pages

Commands

wrap with

let

multiple-value-bind

add import-from

already has a prototype in emacs lisp

move-form-into-let

already has a prototype in emacs lisp

Comment current form

Move top-level form up/down

A.k.a transpose-forms, but keep the cursor at the start of the form that we just moved.

trying to find discrepancies between the packages and test packages

or betweew test system and the system under test

I consider this task “DONE” because I did try to find discrepancies between the package breeze.refactor and breeze.test.refactor. I used the convention that each “command” defined in breeze.refator should have a test with the same name (i.e. the same symbol-name). I have a test that fails if this “invariant” is not held.

In the future, I would like to

Figure out how to generalize “finding missing tests by discrepancies”

Not everyone is going to have the same conventions.

Improve the current test by looking for prefix instead

E.g package a has an exported symbol s, it’s corresponding test package is a.test.

The current implementation would try to find a test named s in a.test (for example a.test::s, or ~”s”~ (test names can be string in parachute), it would be nice to have it also considers tests that have the prefix s.

Why? Because I have some automatically generated test (a bit like snapshot tests), it’s very convenient that they have the same name as the thing they are testing. Using a prefix would let me have multiple kind of tests for each (automatically generated or not).

Do I want to check if each “types” of tests are implemented?

Can parachute’s `deftest`’s be easily augmented with some metadata? That might help too.

Integrate with multiple test framework

See @phoe’s phoe/protest.

Follow up on issues

Add code coverage

Fake packages?

https://github.com/informatimago/lisp/blob/4bfb6893e7840b748648b749b22078f2facfee0a/common-lisp/lisp-reader/package-pac.lisp

Programming with holes

> I was sure I already had a note about these…

Holes, in programming, are something used to tell the language that a part of the program is incomplete. Some languages like Idris and Agda natively support typed holes. The way I see it, holes are used to falicitate the conversation between the programmer and the compiler.

But, for languages like common lisp that doesn’t support holes out-of-the box, how could we do that? In general, there are no symbol name that will never clash with other symbols, because symbols in common lisp can be any string. One idea is to use inline comments, like #| hole-name |#. Breeze’s parser would be able to recognize them and manipulate them.

But what for?

Snippets

Holes can be used to both tell the user what he is expected to enter in a snippet and tell the editor where the user is expected to enter stuff.

Typing

A user could use a hole to tell the editor to infer the type of an expression or function and replace the hole by the appropriate declaration.

Program synthesis

A user could use a hole to tell the editor to find the right expression where the hole is. This probably requires that the user specify some more constraints, by giving types, writing tests, etc.

Tags

Tag descriptions

Tag nameTag description
3rd_partiesRelating to a third-party, e.g. an external library.
obsoleteThis task is now obsolete.
docRelating to the documentation.
uxThis task is about improving the user experience
testThis task is about testing
opsThis task is about CI, releases, deploying docs, etc.
easyThis task should be easy
bugThis is an unintended bug
editorThis task relates to the integration with an editor.
configRelating to breeze’s configuration and setup.
refactorRelating to breeze’s refactoring facilities.
captureRelating to breeze’s capture feature.
quickprojectRelating to quickproject integration.
tech_debtDue to an ongoing refactoring, to an old hack, incomplete implementation, missing tests, etc.
readerRelating to breeze.reader.
noexportorg-mode internal tag
;; (prin1-to-string (org-get-buffer-tags))
;; (prin1-to-string tags)

;; Find tags that have no descriptions
(let ((unknown-tags
       (cl-set-difference
        (mapcar #'car (org-get-buffer-tags))
        (mapcar #'car tags)
        :test #'string=)))
  (or unknown-tags
      "All good, no tags without description found."))

Make sure all tasks have some tags

(let ((result))
  (org-map-entries (lambda ()
                     ;; (org-entry-is-todo-p)
                     (cl-destructuring-bind (level reduced-level todo priority headline _tags)
                         (org-heading-components)
                       ;; _tags does not contain the inherited tags
                       (when (and
                              todo
                              ;; todo could be "DONE" for example
                              (string= todo "TODO")
                              (not (org-get-tags)))
                         (push (list headline) result)))))
  (nreverse result))

Design decisions

Write everything in common lisp

As much as possible, so that breeze can easily be ported to different platforms and editors.

Wrap definitions

Decision: Create wrapper macros (e.g. br:defun) to keep the original forms for later analysis.

This decision is really not definitive.

This decision is less than ideal, especially for existing systems, but it was the easiest to start with.

Alternatives

Keep the string being eval’d

Advising swank’s eval function is “a good start” in that direction.

Parse the source code

Migrate to parachute 2022-03-08

The test framework and the “wrap definition” parts always were proof-of-concepts: I wanted to be able to define some tests, and run them when either the test of the system-under-test was redefined. It worked, but now that I have a more and more complete common lisp parser, I can do the things properly. So I’ve move the concerned code into the folder “scratch-files” and I’ll re-introduce them slowly in the future. (Because I really want something to run the tests in the background, for example.)

Read from strings instead of streams

I did some tests and the code was like 100x faster when reading from string instead of reading from streams. There are multiple reasons: to extract the “raw” text from the stream require consing new strings and abusing file-position to move back and forth in the stream, both of these are very inefficient. Instead, we use displaced arrays which results in way less consing and no “stream state” to manage. This made both the code faster and simpler.

From another point of view: why not? we were already copying the whole stream into the resulting tree, now we just have references to one string.

Use licence and not license

This is a very tiny decision, but I know I’ll forget it.

What made me decide between the two: licence is what asdf use, and it’s what the user will see in their project.

Only use dependencies from quicklisp’s distribution

This project is not in quicklisp, and I don’t plan to add it to quicklisp until it stabilize (which might take years). But I make sure that I only use dependencies from quicklisp so that if somebody wants to try it out they’ll just need to clone this repository in quicklisp’s local-projects folder.

Other projects with slime/sly integration

log4cl

cepl

cl-routes

https://github.com/archimag/cl-routes/blob/master/src/routes-swank.lisp

Portable file watching

https://www.reddit.com/r/lisp/comments/1iatcd/fswatcher_watches_filesystem_changes/

http://eradman.com/entrproject/

https://github.com/Ralt/fs-watcher (polls)

https://github.com/Shinmera/file-notify <===

2023-09-25 I briefly talked with Shinmera this summer, and they mentioned that this project doesn’t currently work.

Random ideas

(tips), (tips “test”), (tips “doc”)

(next) ;; what’s next? print functions that aren’t done, that have no tests or documentation.

functions that aren’t implemented or done

functions that have no tests

functions that have no documentation

Have a plain user-controlled task list

Evaluate quality of documentation

e.g. if the documentation is almost just the name of the function

Make sure it doesn’t “only” refer to another function

It’s more that the content of the function

(defun print-x (x) “print (* x x)” (print (* x x))

Make sure that all package have a :documentation

Make sure that all classes have a :documentation

Evaluation the quality of the code

Cyclomatic complexity

Length of variable names

linting in general

Compare the files in a system’s directory and the actual components.

See BIST to probalistically compare functions

Use a PRNG to generate inputs, use a hash to fingerprint the outputs

See function-fingerprinting.lisp

Generate test for existing functions

  • The more we know the types of the expression, the more we can narrow down the search.
  • It would be easier if we knew which expression are safe to execute

Generate code based on desired input/output

https://github.com/webyrd/Barliman

  • The more we know the types of the expression, the more we can narrow down the search.
  • It would be easier if we knew which expression are safe to execute
  • The linter can help choose better results
  • Using e-graph to refactor candidates can help suggest helper functions

See Programming by examples (inductive synthesis)

A lot of things could be done by instrumenting the code

Which is one of the reason behind wrapping the definitions (e.g. breeze:defun)

  • fault injection
  • program (dynamic) slicing
  • Stepping though code
  • profiling
  • test coverage
  • coverage guided
  • profile-guided optimization

Program slicing

For code navigation

It would be nice to be able to search for something (e.g. calls to make-instance) only in a certain slice (e.g. from the “call tree” of foo).

Correlate with unit tests

If we have multiple tests on the same piece of code, we can use the slices from the tests that pass and the tests that fail to narrow down which slice is probably the source of the failure.

Use equivalence-graph e-graph to suggest refactors

Main resource: E-Graphs Good

This might be hard and complicated, I was thinking that I should start by making this work on a very small scope. For example, if the user ask to suggest some refactors, we can look for forms that contains only arithmetic (again, just an example) and nothing else, that use equality saturation to find interesting equivalent forms and propose them to the user.

Small discussion I had on lobste.rs about e-graphs on lisp

The important bit:

egg is great for algebraic rewrites, but doesn’t have good builtin tools for associative/commutative operations, nor for alpha-equivalence and rewriting under binders. I deliberately left names out of my syntax so that rewriting would be easier; this won’t be as simple for Lisps in general.

I already started working on implementing equivalence graphs

A while ago I started by writing a disjoint sets data structure (also known as union-find, based on the 2 mains operations it supports).

https://github.com/fstamour/disjoint-sets

Semantic diffs using breeze.reader

See

2022-03-17 - I read most of the readme, this system looks awesome

It’s mostly for documentation, but it also expand slime/swank for easier navigation (using the concept of locative).

> Clozure CL provides a way for lisp objects to be watched so that a condition will be signaled when a thread attempts to write to the watched object

Very useful for debugging.

> The advise macro can be thought of as a more general version of trace.

I think I kept this link just for the general interface (advise, unadvise and advisep)

https://github.com/melisgl/mgl-pax for more emacs/slime integration

SLIMA for integration with Atom

Superior Lisp Interactive Mode for Atom

https://github.com/neil-lindquist/SLIMA

An implementation of the Language Server Protocol for Common Lisp

About e-graph

My old elisp snippet to eval with slime and kill the result

https://gist.github.com/fstamour/2d7569beaf42c0a0883dc0ae559c6638

Libraries we might need in the future

PROtocol and TESTcase manager

phoe/protest

PROTEST is a tool for defining protocols and test cases written in and for Common Lisp.

Concrete Syntax Tree

https://github.com/s-expressionists/Concrete-Syntax-Tree This library is intended to solve the problem of source tracking for Common Lisp code.

SICL

A fresh implementation of Common Lisp https://github.com/robert-strandh/SICL

I’m sure there are tons of other user-case:

  • infer types
  • interpret code (symbolically or not)

How froute uses mop to keep track of a set of definitions

froute-class.lisp

Prior Arts

Tinker (1980)

http://web.media.mit.edu/%7Elieber/Lieberary/Tinker/Tinker/Tinker.html

Image Based development

[Image based development](https://www.informatimago.com/develop/lisp/com/informatimago/small-cl-pgms/ibcl/index.html)

Code refactoring tools and libraries, linters, etc.

General

https://comby.dev/ (and https://github.com/s-kostyaev/comby.el) https://github.com/reviewdog/reviewdog

common lisp

https://github.com/hyotang666/trivial-formatter https://github.com/yitzchak/cl-indentify https://github.com/vindarel/colisper (uses comby)

https://github.com/cxxxr/sblint https://github.com/g000001/lisp-critic/ https://github.com/eschulte/lisp-format

javascript and front-end in general

https://github.com/facebookarchive/codemod replaced by https://github.com/facebook/jscodeshift, which uses https://github.com/benjamn/recast

Examples: https://github.com/cpojer/js-codemod

Ruby

https://github.com/whitequark/parser https://github.com/seattlerb/ruby_parser https://github.com/seattlerb/ruby2ruby/ https://docs.rubocop.org/rubocop-ast/node_pattern_compiler.html https://nodepattern.herokuapp.com/ https://github.com/mbj/unparser

Other

Probably Rosely for C# and clang for C/C++. I’m sure there are tons of tools/libraries for Java.

For python, there’s the ast module, but I don’t know if it can preserve the formatting. There’s a bunch of tools to format the code.

Zulu.inuoe’s attempt - clution

Breeze on the internets

Lisp project of the day

https://40ants.com/lisp-project-of-the-day/2020/08/0166-breeze.html

Reddit

https://old.reddit.com/r/Common_Lisp/comments/pgtfm3/looking_for_feedbackhelp_on_a_project/

> testing features along with workers and a file watcher? Shouldn’t they be different projects?

What annoys you when developing in lisp?

I find that setting up a test framework is more difficult than it should be, so any effort on this area is appreciated. I mean: starting with 5am is ok (but could be easier with an editor command), running it from the CLI/a CI is less OK, getting the correct return code of the tests needs more work, etc.

Protocols

  • Chrome DevTools Protocol
  • Slime/Sly
  • LSP (Language Server Protocol)
  • LSIF (Language Server Index Format)
  • Debug Adapter Protocol

To classify

https://quickdocs.org/cl-scripting https://quickdocs.org/repl-utilities slime issue #532: Rename package and all the symbol prefixes https://blog.cddr.org/posts/2021-11-23-on-new-ides/ https://common-lisp.net/project/slime/doc/html/Contributed-Packages.html

https://quickdocs.org/external-symbol-not-found https://github.com/Bike/compiler-macro https://quickdocs.org/dotenv

https://quickdocs.org/slite - SLIME based Test-runner for FiveAM tests (and possibly others in the future)

In SLIME’s debugger, press v to navigate to its definition.

https://github.com/melisgl/journal - for logging and trace-based testing https://github.com/melisgl/try/ - for a test framework that looks a lot with what I want from a test framework.

For a pretty nice review of existing testing framework: https://sabracrolleton.github.io/testing-framework

Emacs Lisp Static Analyzer

https://github.com/ruricolist/moira - Monitor and restart background threads.

https://github.com/pokepay/cl-string-generator - Generate string from regular expression

Emacs supports (declare (pure t) (side-effect-free t))

Sly with spacemacs

https://github.com/mmontone/duologue - High level user interaction library for Common Lis

Discord

Discussion about #: https://discord.com/channels/297478281278652417/569524818991644692/915330555334234192

FAQ from newbies about common lisp

What’s the difference between load and require?

What’s asdf v. quicklisp v. packages v. “os packages”?

The heck is RPLACA?

What’s the difference between setf and setq?

https://stackoverflow.com/questions/869529/difference-between-set-setq-and-setf-in-common-lisp

Why use #:symbol (especially in defpackage)?

Why start a file with (cl:in-package #:cl-user)?

Why interactivity is important?

They don’t actually ask that, they usually just don’t think or know about it.

Here’s something that does an OK job at explaining the importance: https://technotales.wordpress.com/2007/10/03/like-slime-for-vim/

What’s the difference between defvar and defparameter?

Something about using setf to create variables…

A symbol can represent many things

  • variables/symbol macros
  • functions/macros
  • classes/conditions/types
  • method combinations
  • block names
  • catch tags
  • tagbody tags
  • restarts
  • packages
  • compiler macros
  • slot names
  • compiler macros

When coming from another language

How to create a function-local variable?

Proclaim v.s. Declaim v.s. Declare

http://www.lispworks.com/documentation/lw50/LWUG/html/lwuser-90.htm

How packages and symbols works?

https://flownet.com/ron/packages.pdf

Alternatives to the Hyperspec

What the hell are pathnames?

  • Don’t forget trailing backslashes for directories.

Where are the functions to operate on strings?

  • Use the functions that operate on sequences.
  • Use libraries, like alexandria, split-sequences, serapeum, etc.

Glossary

lisp listener

  • More often called “lisp repl”.
  • I use this term to try to avoid confusion with an hypothetical future actual REPL.
  • You could describe that as a “client-server REPL”.

REPl

  • Stands for “Read-Eval-Print-Loop”
  • Most people think about “command line” when they hear REPL, but in the case of lisp, it usually means a “listener”.

Resources

Local variables