├── docs ├── .gitignore ├── Notes-slimv.txt ├── rust_analyzer_blog.org ├── task-generate_web_pages_from_org_files_in_docs.org ├── links_to_some_dependencies_documentation.org ├── silimar_projects.org ├── aartaka.org ├── jedi.org ├── fast_incremental_peg_parsing.org ├── emacs_eat_emulate_a_terminal.org ├── formatting_code.org ├── listener_features.org ├── parse_files_with_conflicts.org ├── vend.org ├── elpy.org ├── missing_documentation.org ├── debug_adapter_protocol.org ├── helping_with_setting_up_slime_for_remote_sessions.org ├── parsing_common_lisp.org ├── emacs_eglot.org ├── testing-breeze.org ├── language_server_protocol.org ├── visual_studio_code_integration.org ├── improve_cl_docstring_s_at_runtime.org ├── file_watching.org ├── goals.org ├── support_for_bug_reports.org ├── case_with_correction_suggestion.org ├── contributing.org ├── flymake.org ├── linting_asd_files.org ├── error_recovery.org ├── editor_integrations.org ├── e_graphs.org ├── slimv.org ├── breeze_on_the_internets.org ├── roadmap.org ├── neovim.org ├── change_impact_analysis.org ├── index.org ├── programming_with_holes.org ├── sly_slime_integration.org ├── getting_started.org ├── glossary.org ├── support_for_tests.org ├── examples_of_code_that_is_not_handled_correctly_by_slime_and_sly.org ├── faq_from_newbies_about_common_lisp.org ├── introduction.org ├── design_decisions.org ├── eclector.org ├── features.org ├── reader-macros.org ├── syntax_highlighting.org └── brightlight-green.css ├── .gitattributes ├── scripts ├── ignore-words.txt ├── setup-quicklisp.lisp ├── demo │ ├── demo.lisp │ ├── setup-demo.lisp │ └── demo-recorder.sh ├── test-ecl.sh ├── test-abcl.sh ├── test-ccl.sh ├── test-sbcl.sh ├── test-clasp.sh ├── manifest-abcl.scm ├── manifest-ecl.scm ├── manifest-sbcl.scm ├── manifest.scm ├── doc.sh ├── run-tests.lisp ├── animate.sh ├── emacs-director.patch ├── compile.sh ├── test-pedantic.sh ├── better-trace.lisp ├── profile-loading.lisp ├── load-dependencies.lisp └── org-publish-project.el ├── .gitmodules ├── tests ├── emacs │ ├── dot-emacs.d │ │ └── early-init.el │ ├── test-straight.el │ ├── no-listener.el │ ├── breeze.erts │ ├── wip-demo.el │ └── breeze-test.el ├── cmds │ └── other-files.lisp ├── logging.lisp ├── channel.lisp ├── utils.lisp ├── pattern │ ├── rewrite.lisp │ └── compile-pattern.lisp ├── buffer.lisp ├── package-commands.lisp ├── package.lisp ├── iterator.lisp ├── dummy-package.lisp ├── xref.lisp ├── workspace.lisp └── report.lisp ├── src ├── doctor.lisp ├── cmds │ ├── blueprint.lisp │ ├── egraph-command.lisp │ ├── command-utils.lisp │ ├── invert.lisp │ ├── quicklisp.lisp │ ├── +quickproject.lisp │ ├── package-commands.lisp │ ├── capture.lisp │ ├── editing.lisp │ ├── completion.lisp │ ├── project.lisp │ └── test-commands.lisp ├── breeze.lisp ├── range.lisp ├── configuration.lisp ├── logging.lisp ├── package.lisp ├── ensure-breeze.lisp ├── channel.lisp ├── pattern │ └── rewrite.lisp ├── thread.lisp ├── xref.lisp └── cl-todo.lisp ├── data └── default-capture-template.lisp ├── .gitignore ├── .dockerignore ├── scratch-files ├── ultra.lisp ├── remote-loading.el ├── utils.lisp ├── temporary-buffer.el ├── wip-find-missing-tests.lisp ├── quicklisp.lisp ├── xref-test.lisp ├── breeze+parachute.lisp ├── bundle.lisp ├── unix-socket.el ├── user-test.lisp ├── try-context-menu-mode.el ├── remote-loading.lisp ├── html.lisp ├── notes │ ├── function-redefinition.lisp │ └── error-system-loadedp.txt ├── edit-distance.lisp ├── xref.lisp ├── analysis.lisp ├── macroexpand.lisp ├── fsa.lisp ├── function-fingerprinting.lisp ├── definition.lisp ├── refactor-scratch.lisp ├── indentation.lisp └── test-runner.lisp ├── .gitlab └── test-job.gitlab-ci.yml ├── .envrc ├── .dir-locals.el ├── README.md ├── LICENSE ├── githooks └── pre-commit ├── .gitlab-ci.yml ├── .github └── workflows │ └── ci.yml └── Dockerfile /docs/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | org-roam.db -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lisp text eol=lf 2 | *.org text eol=lf 3 | -------------------------------------------------------------------------------- /scripts/ignore-words.txt: -------------------------------------------------------------------------------- 1 | upto 2 | falsy 3 | dum 4 | isnt 5 | skipp 6 | whats 7 | clos 8 | iif 9 | te 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scripts/emacs-director"] 2 | path = scripts/emacs-director 3 | url = https://github.com/bard/emacs-director.git 4 | -------------------------------------------------------------------------------- /docs/Notes-slimv.txt: -------------------------------------------------------------------------------- 1 | let g:swank_port=4005 2 | let g:swank_host=localhost 3 | 4 | call SlimvConnectSwank 5 | echom SlimvConnectSwank 6 | 7 | -------------------------------------------------------------------------------- /scripts/setup-quicklisp.lisp: -------------------------------------------------------------------------------- 1 | 2 | (load "quicklisp.lisp") 3 | 4 | (quicklisp-quickstart:install) 5 | 6 | (ql-util:without-prompting 7 | (ql:add-to-init-file)) 8 | -------------------------------------------------------------------------------- /docs/rust_analyzer_blog.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9fdf7c6a-793f-4ea3-b538-0742c7582aa3 3 | :END: 4 | #+title: Rust analyzer blog 5 | 6 | https://rust-analyzer.github.io/blog 7 | -------------------------------------------------------------------------------- /tests/emacs/dot-emacs.d/early-init.el: -------------------------------------------------------------------------------- 1 | 2 | ;; Don't automatically load every package installed 3 | (setq package-enable-at-startup nil) 4 | 5 | (message "Early init file loaded.") 6 | -------------------------------------------------------------------------------- /scripts/demo/demo.lisp: -------------------------------------------------------------------------------- 1 | ;; Setup a lisp process before making a demo 2 | 3 | (cl:in-package #:cl) 4 | 5 | (ql:quickload '(swank breeze)) 6 | 7 | (swank:create-server :port 40050 :dont-close t) 8 | -------------------------------------------------------------------------------- /tests/cmds/other-files.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.tests.other-files 2 | (:documentation "Test package for breeze.other-files.") 3 | (:use #:cl #:breeze.other-files)) 4 | 5 | (in-package #:breeze.tests.other-files) 6 | -------------------------------------------------------------------------------- /scripts/test-ecl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd $(dirname $0)/../ 9 | 10 | exec ecl \ 11 | --eval "(load \"scripts/run-tests.lisp\")" 12 | -------------------------------------------------------------------------------- /scripts/test-abcl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd $(dirname $0)/../ 9 | 10 | exec abcl --batch \ 11 | --eval "(load \"scripts/run-tests.lisp\")" 12 | -------------------------------------------------------------------------------- /scripts/test-ccl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd $(dirname $0)/../ 9 | 10 | exec ccl --batch \ 11 | --eval "(load \"scripts/run-tests.lisp\")" 12 | -------------------------------------------------------------------------------- /src/doctor.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.doctor 2 | (:documentation "Breeze diagnostic") 3 | (:use #:cl)) 4 | 5 | (in-package #:breeze.doctor) 6 | 7 | ;; TODO Check that breeze is configured. 8 | ;; TODO Check the running threads 9 | -------------------------------------------------------------------------------- /docs/task-generate_web_pages_from_org_files_in_docs.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 02d8e1a2-ecea-4c47-8808-b5f7a906b553 3 | :END: 4 | #+title: Generate web pages from org files in docs/ 5 | 6 | * TODO Generate web pages from org files in docs/ 7 | -------------------------------------------------------------------------------- /data/default-capture-template.lisp: -------------------------------------------------------------------------------- 1 | 2 | (ql:quickload '(alexandria)) 3 | 4 | ;; Make it easier to debug 5 | (declaim (optimize (speed 0) (safety 3) (debug 3))) 6 | 7 | #| 8 | 9 | Goal: 10 | 11 | Motivation: 12 | 13 | First lead: 14 | 15 | |# 16 | -------------------------------------------------------------------------------- /scripts/test-sbcl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd $(dirname $0)/../ 9 | 10 | exec sbcl --noinform --non-interactive \ 11 | --eval "(load \"scripts/run-tests.lisp\")" 12 | -------------------------------------------------------------------------------- /tests/emacs/test-straight.el: -------------------------------------------------------------------------------- 1 | ;; Work in progress: test that breeze is loadable with straight 2 | 3 | (load (find-library-name "straight")) 4 | 5 | (straight-use-package 6 | '(breeze :type git :host codeberg :repo "fstamour/breeze" 7 | :files "emacs)) 8 | -------------------------------------------------------------------------------- /scripts/test-clasp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd "$(git rev-parse --show-toplevel)" 9 | 10 | exec clasp --non-interactive \ 11 | --eval "(load \"scripts/run-tests.lisp\")" 12 | -------------------------------------------------------------------------------- /docs/links_to_some_dependencies_documentation.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 7d0f5cd2-d216-4882-84ac-27c004ad6fbd 3 | :END: 4 | #+title: Links to some dependencies' documentation 5 | * Resources 6 | 7 | - [[https://alexandria.common-lisp.dev/draft/alexandria.html][alexandria]] 8 | -------------------------------------------------------------------------------- /docs/silimar_projects.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 62112623-6002-4cb9-87de-cb530ce0a36e 3 | :END: 4 | #+title: Silimar projects 5 | 6 | * Python 7 | 8 | - [[id:5eb1faac-b7b5-4c99-abe9-b91e77bea4ae][Jedi]] 9 | - [[id:5265bce5-c6d0-4cda-8d3e-699ceafcab42][Elpy]] 10 | -------------------------------------------------------------------------------- /docs/aartaka.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 52254263-fae9-4ead-8467-110735b07a2a 3 | :END: 4 | #+title: AArtaka 5 | 6 | a.k.a. Artyom Bologov 7 | 8 | https://github.com/aartaka/graven-image/tree/master 9 | 10 | https://github.com/aartaka/lisp-config/blob/master/ed.lisp 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.fasl 2 | *.core 3 | 4 | # ecl 5 | *.fas 6 | 7 | *.svg 8 | result 9 | public/ 10 | 11 | # org-roam db 12 | *.db 13 | 14 | githooks/*.sample 15 | /.direnv/ 16 | *.dot 17 | 18 | *.png 19 | *.log 20 | *~ 21 | /docs/sitemap.org 22 | 23 | build/ 24 | *.tar 25 | *.tar.gz -------------------------------------------------------------------------------- /scripts/manifest-abcl.scm: -------------------------------------------------------------------------------- 1 | (specifications->manifest 2 | (list 3 | "coreutils" 4 | "bash" 5 | 6 | "cl-bordeaux-threads" 7 | "cl-alexandria" 8 | "cl-parachute" 9 | "cl-spinneret" 10 | "cl-closer-mop" 11 | "cl-ppcre" 12 | "cl-quickproject" 13 | 14 | "abcl")) 15 | -------------------------------------------------------------------------------- /docs/jedi.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 5eb1faac-b7b5-4c99-abe9-b91e77bea4ae 3 | :END: 4 | #+title: Jedi 5 | 6 | #+begin_quote 7 | Jedi - an awesome autocompletion, static analysis and refactoring 8 | library for Python 9 | #+end_quote 10 | 11 | https://jedi.readthedocs.io/en/latest/ 12 | -------------------------------------------------------------------------------- /scripts/manifest-ecl.scm: -------------------------------------------------------------------------------- 1 | (specifications->manifest 2 | (list 3 | "coreutils" 4 | "bash" 5 | 6 | "ecl-bordeaux-threads" 7 | "ecl-alexandria" 8 | "ecl-parachute" 9 | "ecl-spinneret" 10 | "ecl-closer-mop" 11 | "ecl-cl-ppcre" 12 | "ecl-quickproject" 13 | 14 | "ecl")) 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # *.core 2 | *.dot 3 | *.fasl 4 | *.log 5 | *.org 6 | *.png 7 | *.svg 8 | *~ 9 | .dockerignore 10 | .git/ 11 | .gitattributes 12 | .githooks/ 13 | .github/ 14 | .gitignore 15 | .gitmodules 16 | /.direnv/ 17 | dockerfile 18 | githooks/*.sample 19 | public/ 20 | result 21 | scratch-files/ -------------------------------------------------------------------------------- /docs/fast_incremental_peg_parsing.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: a1b34261-e041-412a-a291-c26fd0e34668 3 | :END: 4 | #+title: Fast Incremental PEG Parsing 5 | 6 | https://www.youtube.com/watch?v=k3vBR58u3cY 7 | 8 | Presented at SLE 2021, part of SPLASH 2021. 9 | By Zachary Yedidia, Stephen Chong 10 | -------------------------------------------------------------------------------- /scripts/manifest-sbcl.scm: -------------------------------------------------------------------------------- 1 | (specifications->manifest 2 | (list 3 | "coreutils" 4 | "bash" 5 | 6 | "sbcl-bordeaux-threads" 7 | "sbcl-alexandria" 8 | "sbcl-parachute" 9 | "sbcl-spinneret" 10 | "sbcl-closer-mop" 11 | "sbcl-cl-ppcre" 12 | "sbcl-quickproject" 13 | 14 | "sbcl")) 15 | -------------------------------------------------------------------------------- /docs/emacs_eat_emulate_a_terminal.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 1af469ee-d6ec-46fe-a335-b044e142eb7d 3 | :END: 4 | #+title: Emacs eat - Emulate a terminal 5 | 6 | Emulate A Terminal, in a region, in a buffer and in Eshell 7 | 8 | https://codeberg.org/akib/emacs-eat 9 | 10 | Seems interesting... 11 | -------------------------------------------------------------------------------- /docs/formatting_code.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: d487821d-01e7-41a1-b9db-1a856fd7eb01 3 | :END: 4 | #+title: Formatting code 5 | 6 | https://github.com/radian-software/apheleia 7 | 8 | 🌷 Run code formatter on buffer contents without moving point, using 9 | RCS patches and dynamic programming. 10 | -------------------------------------------------------------------------------- /docs/listener_features.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: d21da464-7b9e-47d2-bc2a-c9ab7a927218 3 | :END: 4 | #+title: Listener features 5 | 6 | condition "package not found" => 7 | - try to fix spelling 8 | - load a system or file that contains the package 9 | - create a new system or file to create the 10 | -------------------------------------------------------------------------------- /docs/parse_files_with_conflicts.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: da4c20d5-9947-434c-909e-3e766357244f 3 | :END: 4 | #+title: Parse files with conflicts 5 | 6 | It would be nice if the parser was able to detect git's conflict 7 | markers. 8 | 9 | It would be ever nicer if breeze could help fixing those conflicts. 10 | -------------------------------------------------------------------------------- /scratch-files/ultra.lisp: -------------------------------------------------------------------------------- 1 | (ql-dist:install-dist "http://dist.ultralisp.org/" 2 | :prompt nil) 3 | 4 | 5 | ;;; Uninstall ultralisp 6 | #+ (or) 7 | (ql-dist:uninstall 8 | (find-if #'(lambda (dist) 9 | (string= "ultralisp" 10 | (ql-dist:name dist))) 11 | (ql-dist:all-dists))) 12 | -------------------------------------------------------------------------------- /docs/vend.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: d306a11f-4189-4e24-9a26-0b2cb813e54e 3 | :ROAM_ALIASES: vend 4 | :END: 5 | #+title: fosskers/vend 6 | #+filetags: :tool: 7 | 8 | #+begin_quote 9 | Manage your Common Lisp project dependencies. 10 | #+end_quote 11 | 12 | - [[https://github.com/fosskers/vend][Homepage on GitHub]] 13 | -------------------------------------------------------------------------------- /tests/emacs/no-listener.el: -------------------------------------------------------------------------------- 1 | (require 'ert) 2 | (require 'ert-x) 3 | 4 | (ert-deftest breeze-init-no-listener-loaded () 5 | "Make sure that `breeze-init' has sane behaviour if it can't find sly or slime." 6 | (should (equal 7 | '(error "Please start either slime or sly.") 8 | (should-error (breeze-init))))) 9 | -------------------------------------------------------------------------------- /docs/elpy.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 5265bce5-c6d0-4cda-8d3e-699ceafcab42 3 | :END: 4 | #+title: Elpy 5 | 6 | #+begin_quote 7 | Elpy is an extension for the Emacs text editor to work with Python 8 | projects. 9 | #+end_quote 10 | 11 | https://elpy.readthedocs.io/en/latest/ 12 | 13 | - Uses [[id:5eb1faac-b7b5-4c99-abe9-b91e77bea4ae][Jedi]] 14 | -------------------------------------------------------------------------------- /docs/missing_documentation.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 018d969c-f075-406a-95a0-d4bac06df069 3 | :END: 4 | #+title: Missing Documentation 5 | 6 | I do have some utilities to flag missing documentation. 7 | 8 | I found out that Zulu' too made something: 9 | 10 | https://gitlab.com/Zulu-Inuoe/common.lisp/-/blob/4d326828efb78c6518fc2364021a143ec4c98b8b/utils.lisp#L283 11 | -------------------------------------------------------------------------------- /docs/debug_adapter_protocol.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 44f1edb0-d65f-4e6c-bd87-5bed4fc07376 3 | :END: 4 | #+title: Debug Adapter Protocol 5 | 6 | Not unlike LSP (Language Server Protocol), but for debugger. 7 | 8 | - [[https://microsoft.github.io/debug-adapter-protocol/][Official web page]] 9 | - [[https://github.com/svaante/dape][Dape - Debug Adapter Protocol for Emacs]] 10 | -------------------------------------------------------------------------------- /docs/helping_with_setting_up_slime_for_remote_sessions.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: b139c21c-3a35-4b69-acd5-00b9d71090ce 3 | :END: 4 | #+title: Helping with setting up slime for remote sessions 5 | 6 | It would be nice if breeze could help with detecting misconfigured (or 7 | not configured) file path translation (=slime-filename-translations=). 8 | 9 | Most people just don't know this feature exists. 10 | -------------------------------------------------------------------------------- /docs/parsing_common_lisp.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: edbd3cb1-e04b-41f9-b35f-20c123854481 3 | :END: 4 | #+title: Parsing Common Lisp 5 | 6 | * Concrete Syntax Tree 7 | :PROPERTIES: 8 | :ID: 1f979dc4-a4b7-4223-af54-82fa3725c8a3 9 | :END: 10 | 11 | https://github.com/s-expressionists/Concrete-Syntax-Tree 12 | 13 | This library is intended to solve the problem of source tracking for 14 | Common Lisp code. 15 | -------------------------------------------------------------------------------- /scratch-files/remote-loading.el: -------------------------------------------------------------------------------- 1 | ;;;; See remote-loading.lisp 2 | 3 | (defun get-string-from-file (filePath) 4 | "Return file content as string." 5 | (with-temp-buffer 6 | (insert-file-contents filePath) 7 | (buffer-string))) 8 | 9 | (breeze-eval 10 | (concat "(progn" 11 | (get-string-from-file 12 | (breeze-relative-path "scratch-files/remote-loading.lisp")) 13 | "'loaded)")) 14 | -------------------------------------------------------------------------------- /docs/emacs_eglot.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 38d6dbd7-0580-4701-bd52-ee97174a0535 3 | :END: 4 | #+title: Emacs' eglot 5 | 6 | Included in emacs since version 29. 7 | 8 | - =eglot--propose-changes-as-diff= 9 | - =eglot-forget-pending-continuations= 10 | 11 | * Related notes 12 | 13 | - [[id:9d5bc298-56d4-40c4-af2e-5b127d5914bf][Language Server Protocol]] 14 | - [[id:44f1edb0-d65f-4e6c-bd87-5bed4fc07376][Debug Adapter Protocol]] 15 | -------------------------------------------------------------------------------- /docs/testing-breeze.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: e712f3d1-0734-43f0-886a-3008ca5f722d 3 | :END: 4 | #+title: Testing Breeze 5 | 6 | * How to run the tests 7 | 8 | #+begin_src lisp 9 | (ql:quickload "breeze/test") 10 | (asdf:test-system "breeze") 11 | #+end_src 12 | 13 | Or from the command line: 14 | 15 | #+begin_src shell 16 | ./scripts/test-sbcl.sh 17 | #+end_src 18 | 19 | OR 20 | 21 | #+begin_src shell 22 | make test 23 | #+end_src 24 | -------------------------------------------------------------------------------- /src/cmds/blueprint.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.blueprint 3 | (:documentation "Snippets, but better™") 4 | (:use #:cl)) 5 | 6 | (in-package #:breeze.blueprint) 7 | 8 | ;; TODO make "breeze-quickinsert" choose to insert a defun when there's a "defun" at point... 9 | #| 10 | 11 | defun 12 | 13 | 14 | defmethod insert-pattern 15 | 16 | patterns need docstring/documentation/prompt 17 | 18 | insert-symbol 19 | ensure-sym (convert symbol to sym) 20 | |# 21 | -------------------------------------------------------------------------------- /.gitlab/test-job.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | inputs: 3 | lisp-impl: 4 | default: sbcl 5 | --- 6 | test-$[[ inputs.lisp-impl ]]: 7 | image: registry.gitlab.com/fstamour/breeze/breeze-ci-$[[ inputs.lisp-impl ]]:latest 8 | script: 9 | - $[[ inputs.lisp-impl ]] --load scripts/quicklisp.lisp 10 | --eval "(quicklisp-quickstart:install)" 11 | --eval "(ql-util:without-prompting (ql:add-to-init-file))" 12 | - scripts/test-$[[ inputs.lisp-impl ]].sh 13 | -------------------------------------------------------------------------------- /docs/language_server_protocol.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9d5bc298-56d4-40c4-af2e-5b127d5914bf 3 | :END: 4 | #+title: Language Server Protocol 5 | 6 | * TODO An implementation of the Language Server Protocol for Common Lisp :editor: 7 | 8 | - https://github.com/cxxxr/cl-lsp 9 | - related: https://marketplace.visualstudio.com/items?itemName=ailisp.commonlisp-vscode 10 | 11 | 12 | https://github.com/manateelazycat/lsp-bridge 13 | A blazingly fast LSP client for Emacs 14 | -------------------------------------------------------------------------------- /src/breeze.lisp: -------------------------------------------------------------------------------- 1 | 2 | (uiop:define-package #:breeze 3 | (:documentation "The breeze package meant for the end-user.") 4 | (:use #:cl) 5 | (:use-reexport) 6 | (:import-from #:breeze.command 7 | #:define-command) 8 | (:export #:define-command)) 9 | 10 | (in-package #:breeze) 11 | 12 | #++ 13 | (let ((package #:breeze.command) 14 | (symbols '(#:define-command 15 | #:read-string-then-insert))) 16 | (loop import then export)) 17 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | 2 | # TODO Those are common lips implementations found in guix, I'm unsure 3 | # about their name in nixpkgs 4 | # UNSURE="clisp ecl abcl gcl ccl" 5 | 6 | # TODO cl-all https://github.com/Shinmera/cl-all 7 | 8 | PKGS_COMMON="sbcl bash entr fd" 9 | 10 | NIX_PKGS="gnumake codespell" 11 | GUIX_PKGS="make python-codespell" 12 | 13 | 14 | if has guix; then 15 | use guix $PKGS_COMMON $GUIX_PKGS 16 | elif has nix-shell; then 17 | use nix -p $PKGS_COMMON $NIX_PKGS 18 | fi 19 | -------------------------------------------------------------------------------- /docs/visual_studio_code_integration.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 086c7705-e5ec-4dc0-852d-211c055eb145 3 | :END: 4 | #+title: Visual Studio Code integration 5 | 6 | NOT IMPLEMENTED, this is a design document (for the moment) 7 | 8 | * Existing vscode extension for Common Lisp 9 | 10 | ** TODO ALIVE 11 | 12 | * Related notes 13 | 14 | - [[id:6bd2b06d-0a3c-4d32-9a1e-4f6f36e1003d][Emacs integration]] 15 | - [[id:f3a9c9a2-8180-43a8-9424-e66fd6190caa][Vim and Neovim integration]] 16 | -------------------------------------------------------------------------------- /tests/emacs/breeze.erts: -------------------------------------------------------------------------------- 1 | 2 | Point-Char: | 3 | 4 | Name: insert-in-package-cl-user 5 | Code: breeze-insert-in-package-cl-user 6 | 7 | =-= 8 | | 9 | =-= 10 | (cl:in-package #:cl-user) 11 | =-=-= 12 | 13 | 14 | ;; WIP 15 | ;; Name: insert-defun 16 | ;; Code: breeze-insert-defun 17 | 18 | ;; =-= 19 | ;; | 20 | ;; =-= 21 | ;; (defun a (b c) 22 | ;; ) 23 | ;; =-=-= 24 | 25 | 26 | ;; Corrections of typos 27 | ;; =-= 28 | ;; |(lost 1 2 3) 29 | ;; =-= 30 | ;; |(list 1 2 3) 31 | ;; =-=-= 32 | -------------------------------------------------------------------------------- /docs/improve_cl_docstring_s_at_runtime.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9dbbf418-de72-4d31-8347-19e3dc7d8df1 3 | :END: 4 | #+title: Improve CL docstring's at runtime 5 | 6 | I noticed that, on sbcl, you can =(setf (documentation 'x 'function) 7 | ...= on symbols that are part of the =cl= package (which I didn't 8 | expect because of the package lock). It _could_ be nice to load a 9 | package during development that adds examples to the docstrings, and 10 | perhaps even "links" between the defintions? 11 | -------------------------------------------------------------------------------- /docs/file_watching.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 373c4a22-b450-40e1-8d00-1319e0277b68 3 | :END: 4 | #+title: File watching 5 | * Portable file watching 6 | 7 | https://www.reddit.com/r/lisp/comments/1iatcd/fswatcher_watches_filesystem_changes/ 8 | 9 | http://eradman.com/entrproject/ 10 | 11 | https://github.com/Ralt/fs-watcher (polls) 12 | 13 | https://github.com/Shinmera/file-notify <=== 14 | 15 | 2023-09-25 I briefly talked with Shinmera this summer, and they 16 | mentioned that this project doesn't currently work. 17 | -------------------------------------------------------------------------------- /scripts/manifest.scm: -------------------------------------------------------------------------------- 1 | ;; this manifest is meant for running integration tests 2 | 3 | ;; TODO variants: 4 | ;; - per lisp implementation 5 | ;; - sly vs slime 6 | ;; - emacs, nvim, vscode 7 | ;; - x vs no-x 8 | 9 | (specifications->manifest 10 | (list 11 | ;; "emacs-no-x" 12 | "emacs" "scrot" 13 | "coreutils" 14 | 15 | ;; "cl-slime" 16 | "emacs-slime" 17 | "emacs-sly" 18 | "cl-bordeaux-threads" 19 | "cl-alexandria" 20 | 21 | "sbcl" 22 | "abcl" 23 | "ecl" 24 | "clisp" 25 | "clasp-cl" 26 | "gcl" 27 | "ccl")) 28 | -------------------------------------------------------------------------------- /docs/goals.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: e5d64314-8b13-4a6b-997f-1aae94910d63 3 | :END: 4 | #+title: Goals and non-goals 5 | 6 | * Goals 7 | 8 | - Make it easier to develop in common lisp 9 | - by any means 10 | - With any editor (or even without one) 11 | - Be as portable as possible 12 | - Be useful to new and experimented developer (or even 13 | non-developpers, we'll get there) 14 | 15 | * Non-goals 16 | 17 | - Replace slime, sly, slimv, slima, etc 18 | - Replace existing test framework 19 | - Force the user to use a set of conventions 20 | -------------------------------------------------------------------------------- /scratch-files/utils.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defmacro chain (&body forms) 3 | (reduce (lambda (acc next) 4 | (append acc (list next))) 5 | (butlast forms) 6 | :initial-value (alexandria:lastcar forms) 7 | :from-end t)) 8 | 9 | (defmacro chain* (&body forms) 10 | (alexandria:with-gensyms (callback) 11 | `(lambda (,callback) 12 | (chain ,@forms ,callback)))) 13 | 14 | (defun reverse-parameter (fn) 15 | "Take a function of arity 2 and call return a function with the 2 16 | parameters inverted." 17 | #'(lambda (x y) (funcall fn y x))) 18 | -------------------------------------------------------------------------------- /scripts/doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to generate the documentation 4 | # 5 | 6 | set -e 7 | 8 | cd "$(git rev-parse --show-toplevel)" 9 | 10 | mkdir -p public/ 11 | 12 | sbcl --noinform --non-interactive \ 13 | --eval "(declaim (optimize (debug 3) (speed 0) (safety 3)))" \ 14 | --eval "(asdf:load-asd (truename \"breeze.asd\"))" \ 15 | --eval "(ql:quickload '#:breeze/doc)" \ 16 | --eval "(ql:quickload '#:breeze/dogfood)" \ 17 | --eval '(breeze.dogfood:generate-breeze-reference)' 18 | 19 | cp docs/style.css public/ 20 | -------------------------------------------------------------------------------- /scripts/run-tests.lisp: -------------------------------------------------------------------------------- 1 | (require 'asdf) 2 | 3 | #-quicklisp 4 | (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" 5 | (user-homedir-pathname)))) 6 | (if (probe-file quicklisp-init) 7 | (load quicklisp-init) 8 | (warn "~%~%======~%Quicklisp is not installed.~%======~%~%"))) 9 | 10 | (asdf:load-asd (truename "breeze.asd")) 11 | 12 | #+quicklisp 13 | (ql:quickload "breeze/test" :verbose t) 14 | 15 | #-quicklisp 16 | (asdf:load-system "breeze/test") 17 | 18 | (breeze.test.main:run-all-tests :exitp t) 19 | -------------------------------------------------------------------------------- /scratch-files/temporary-buffer.el: -------------------------------------------------------------------------------- 1 | (defvar breeze-temporary-buffers '()) 2 | 3 | (defun breeze-make-temporary-buffer () 4 | "Make a buffer name, add it to the list." 5 | (let ((buffer-name (make-temp-name (concat "tmp-" (format-time-string "%Y-%m.%dT%H.%M.%S-"))))) 6 | (push buffer-name breeze-temporary-buffers) 7 | buffer-name)) 8 | 9 | (defun breeze-kill-all-temporary-buffer () 10 | "Kill all buffers." 11 | (interactive) 12 | (dolist (buffer-name breeze-temporary-buffers) 13 | (when (get-buffer buffer-name) 14 | (kill-buffer buffer-name))) 15 | (setf breeze-temporary-buffers '())) 16 | -------------------------------------------------------------------------------- /scratch-files/wip-find-missing-tests.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package #:breeze.user) 3 | 4 | (defun check-for-untested-functions () 5 | (let ((missing-tests 6 | (loop 7 | :for package :in (current-packages) 8 | :append (breeze.xref::function-without-test package)))) 9 | (if missing-tests 10 | (progn 11 | (princ "There are untested functions in current packages:") 12 | (format t "~&~{ * ~A~%~}" 13 | missing-tests)) 14 | (format t "~&No untested function found. ~A" (cheers))))) 15 | 16 | (loop 17 | :for package :in (current-packages) 18 | :append (breeze.xref::function-without-test package)) 19 | -------------------------------------------------------------------------------- /docs/support_for_bug_reports.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 1bfee55a-11ef-47d6-924b-2ce1a9b39f3b 3 | :END: 4 | #+title: Support for bug reports 5 | 6 | * TODO Make it easy to report a bugs :ux:ops: 7 | 8 | - OS version 9 | - lisp version 10 | - editor version 11 | - quicklisp 12 | - client version 13 | - distributions 14 | - for each dependency 15 | - from which distribution the system come from 16 | - version of the system 17 | 18 | * Generalized support for bug reports :idea: 19 | 20 | Maybe there could be a way to report bugs for any projects (not just 21 | breeze)? 22 | -------------------------------------------------------------------------------- /tests/logging.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.test.logging 2 | (:documentation "Tests for breeze.logging.") 3 | (:use #:cl) 4 | (:import-from #:breeze.logging 5 | #:compare-level) 6 | (:import-from #:parachute 7 | #:define-test 8 | #:define-test+run 9 | #:is 10 | #:true 11 | #:false)) 12 | 13 | (in-package #:breeze.test.logging) 14 | 15 | (define-test+run compare-level 16 | (false (compare-level #'< :debug :debug)) 17 | (false (compare-level #'< :info :debug)) 18 | (true (compare-level #'< :debug :critical)) 19 | (false (compare-level #'< :critical :debug))) 20 | -------------------------------------------------------------------------------- /scripts/animate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # This is an example of how to take a bunch of capture made with tmux 4 | # and assemble them with termtosvg 5 | # 6 | # usage: termtosvg output.svg -c ./animate.sh 7 | # or: termtosvg $demo_root -s -c ./animate.sh 8 | # 9 | 10 | # Stop on first error 11 | set -e 12 | 13 | # Move to repo's root 14 | cd "$(git rev-parse --show-toplevel)" 15 | 16 | demo_root=scripts/demo/ 17 | 18 | if [ ! -d "$demo_root" ]; then 19 | echo "Demo folder doesn't exits" 20 | exit 1 21 | fi 22 | 23 | for capture in $(echo $demo_root/*.capture | sort -n) ; do 24 | # echo "$capture" 25 | cat "$capture" 26 | sleep 0.5 27 | done 28 | -------------------------------------------------------------------------------- /src/cmds/egraph-command.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.egraph-command 3 | (:documentation "Commands that use equivalence graphs (e-graphs) to refactor code.") 4 | (:use #:cl #:breeze.analysis) 5 | (:import-from #:breeze.command 6 | #:define-command 7 | #:message) 8 | (:import-from #:breeze.command-utils 9 | #:pulse-node 10 | #:current-node)) 11 | 12 | (in-package #:breeze.egraph-command) 13 | 14 | ;; "E-Graph Good" → egg →🥚 → scramble 15 | (export 16 | (define-command scramble () 17 | "Scramble the code at point." 18 | (alexandria:when-let (($node (current-node))) 19 | (message (node-string $node))))) 20 | -------------------------------------------------------------------------------- /docs/case_with_correction_suggestion.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: f50fbc39-3148-4ecb-8e46-719ef3e05fd8 3 | :END: 4 | #+title: Case with correction suggestion 5 | 6 | Image a variant of =cl:ecase= that find the nearest match and provides 7 | a corresponding restart for it. 8 | 9 | #+begin_src lisp 10 | (let ((x 'boo)) 11 | (case x 12 | (foo ...) 13 | (bar ...))) 14 | ;; => error: 'boo is not one of '(foo bar) 15 | ;; => restart: use 'foo instead 16 | #+end_src 17 | 18 | What if there are many options with the same edit distance? 19 | 20 | 21 | This might be useful: 22 | "Wrap implementation-dependent accessor for restarts in CL" 23 | https://git.sr.ht/~yana/trivial-restart-accessors/ 24 | -------------------------------------------------------------------------------- /docs/contributing.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 279c4ea6-2004-4a7a-a2c9-905f27fae42c 3 | :END: 4 | #+title: Contributing 5 | 6 | * Contributing 7 | 8 | Start by forking and cloning this repository (e.g. into quicklisp's 9 | local-projects directory). 10 | 11 | Setup the pre-commit hook 12 | 13 | #+begin_src shell 14 | git config core.hooksPath githooks 15 | #+end_src 16 | 17 | Look for TODOs in the code 18 | 19 | #+begin_src shell 20 | grep -ir --include='*.lisp' todo 21 | # or 22 | rg -i todo 23 | #+end_src 24 | 25 | Explore the documentation. 26 | 27 | * Related notes 28 | 29 | - [[id:14d42b3a-0a2f-4a3b-8937-7175e621c6ec][Design Decisions]] 30 | - [[id:11dd9906-75ff-4abc-82a5-b7dda0936f06][Roadmap]] 31 | -------------------------------------------------------------------------------- /docs/flymake.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 5b8f3aee-0ea4-4688-8de7-e0b3ac140405 3 | :END: 4 | #+title: Flymake 5 | 6 | flymake-show-buffer-diagnostics 7 | flymake-show-project-diagnostics 8 | 9 | next-error-function 10 | 11 | - https://www.gnu.org/software/emacs/manual/html_node/flymake/Troubleshooting.html 12 | - https://www.gnu.org/software/emacs/manual/html_node/flymake/Backend-functions.html 13 | - https://www.gnu.org/software/emacs/manual/html_node/flymake/An-annotated-example-backend.html 14 | 15 | * Troubleshooting 16 | 17 | use =C-u M-x flymake-start= to reset the disabled backends 18 | 19 | * Related notes 20 | 21 | - [[id:f8811c6f-9813-418f-a745-72be32add601][Syntax checking and linting in neovim]] 22 | -------------------------------------------------------------------------------- /docs/linting_asd_files.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: e55cab7e-beb6-4cb4-be2e-d0d78a8f568a 3 | :END: 4 | #+title: Linting asd files 5 | 6 | * Use =(in-package #:asdf-user)= at the start of the file 7 | 8 | Using (interned) symbols in the .asd files _might_ intern symbols in 9 | =*package*= (especially if someone tries to =(read ...)= the =.asd= 10 | file. 11 | 12 | * Look for missing files 13 | 14 | I could see one nice workflow: 15 | 1. Open the system defintion 16 | 2. Add a file in the =:components= list 17 | 3. The linter highlights the missing file 18 | 4. The user choose a "code action" that creates the file and opens it 19 | in the editor, perhaps even adding a package definition at the 20 | start. 21 | -------------------------------------------------------------------------------- /docs/error_recovery.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9eee6140-644d-4615-ae88-be84243f63af 3 | :END: 4 | #+title: Error recovery 5 | 6 | - basic: use "synchronization points", places where it looks like a 7 | good place to restart parsing after an invalid parse 8 | 9 | - it would be much easier to pin-point the source of the failure if we 10 | start from a previous good state (incremental parsing) 11 | 12 | - I noticed that for lisp, a lot of things would be "easy" to parse 13 | backward, this would help tremendously pin-pointing where a "bad 14 | parse" begins. 15 | 16 | 17 | 18 | An easy case for error recovery: 19 | 20 | #+begin_src lisp 21 | #| 22 | it's missing a closing "pipehash" before the end of the string 23 | #+end_src 24 | -------------------------------------------------------------------------------- /src/cmds/command-utils.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.command-utils 2 | (:documentation "Utilities to write commands") 3 | (:use #:cl #:breeze.command #:breeze.analysis) 4 | (:export #:pulse-node 5 | #:current-node)) 6 | 7 | (in-package #:breeze.command-utils) 8 | 9 | (defun node-at-point ()) 10 | 11 | ;; TODO This should go in a file for "commands that uses parse trees" 12 | (defun pulse-node (node) 13 | (pulse (start node) (end node))) 14 | 15 | (defun current-node (&key pulsep) 16 | (alexandria:when-let* 17 | ((buffer (current-buffer)) 18 | (node-iterator (node-iterator buffer))) 19 | (when pulsep (pulse-node node-iterator)) 20 | node-iterator)) 21 | 22 | ;; TODO Maybe make a command "replace-form" or "replace-car" 23 | -------------------------------------------------------------------------------- /docs/editor_integrations.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 5d211d9a-0749-4adb-abe0-e66133d09b5b 3 | :END: 4 | #+title: Editor integrations 5 | 6 | - [[id:6bd2b06d-0a3c-4d32-9a1e-4f6f36e1003d][Emacs integration]] 7 | - [[id:086c7705-e5ec-4dc0-852d-211c055eb145][Visual Studio Code integration]] (not implemented) 8 | - [[id:f3a9c9a2-8180-43a8-9424-e66fd6190caa][Neovim]] (not implemented) 9 | 10 | * TODO Make sure the commands are executed against _common lisp_ code :ux:editor: 11 | 12 | Gavinok tried breeze, but ran ~C-.~ on ~breeze.el~, this caused 13 | confusion for everybody. 14 | 15 | Here's some ideas to help with this: 16 | 17 | - check the extension of the buffer or file 18 | - check the mode of the buffer 19 | - make sure it's not in the repl (for now) 20 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables -*- no-byte-compile: t -*- 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((nil . ((eval . (progn 5 | (setq-local org-roam-directory 6 | (file-truename 7 | (file-name-concat 8 | (locate-dominating-file default-directory ".dir-locals.el") 9 | "docs/"))) 10 | (setq-local org-roam-db-location 11 | (file-name-concat org-roam-directory "org-roam.db")))) 12 | (org-roam-capture-templates . (("d" "default" plain "%?" :target (file+head "${slug}.org" "#+title: ${title} 13 | ") :unnarrowed t)))))) 14 | -------------------------------------------------------------------------------- /src/cmds/invert.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.invert 3 | (:documentation "Command to invert the form at point.") 4 | (:use #:cl)) 5 | 6 | (in-package #:breeze.invert) 7 | 8 | #++ 9 | (unless (donep $node) 10 | (cond 11 | ((match (sym :wild t) $node) 12 | ;; replace by nil 13 | ) 14 | ((match (sym :wild nil) $node) 15 | ;; replace by t 16 | ))) 17 | 18 | ;; TODO true <=> false 19 | ;; TODO if => swap then-form and else-form, or invert the condition 20 | ;; TODO < <=> >= 21 | ;; TODO > <=> <= 22 | ;; TODO = >= /= 23 | ;; TODO eq => (not eq) 24 | ;; TODO while => until 25 | ;; TODO when => unless (or invert the condition) 26 | ;; TODO (null x) <=> x 27 | ;; TODO {string,char} {=,-{,not-}equal,<,<=,>,>=,-{,not-}lessp,-{,not-}greaterp 28 | ;; TODO is <=> isnt (parachute) 29 | -------------------------------------------------------------------------------- /scripts/demo/setup-demo.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.setup-demo 2 | (:documentation "Load and configure everything needed for a demonstration.") 3 | (:use #:cl)) 4 | 5 | (in-package #:breeze.setup-demo) 6 | 7 | (ql:quickload "swank") 8 | 9 | ;; See the content of the variable slime-required-modules in emacs to 10 | ;; get the list available modules. See the "needed" variable of 11 | ;; #'slime-load-contribs to know which ones are actually needed. 12 | ;; 13 | ;; Those are the ones required (transitively) by the slime-fancy 14 | ;; contrib. 15 | (swank:swank-require 16 | '(#:swank-indentation 17 | #:swank-trace-dialog 18 | #:swank-package-fu 19 | #:swank-presentations 20 | #:swank-macrostep 21 | #:swank-fuzzy 22 | #:swank-fancy-inspector 23 | #:swank-c-p-c 24 | #:swank-arglists 25 | #:swank-repl)) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breeze 2 | 3 | - [codeberg](https://codeberg.org/fstamour/breeze) 4 | - main repo 5 | - [gitlab](https://gitlab.com/fstamour/breeze) 6 | - (as of 2025-11-30) used for CI and documentation hosting (gitlab 7 | pages) 8 | - [github](https://github.com/fstamour/breeze) 9 | - (as of 2025-11-30) used for better discoverability 10 | 11 | Breeze is a set of tools that aims to make lisp development a breeze 12 | (hence the name). 13 | 14 | It is very much alpha quality, I'm experimenting with a lot of things 15 | in parallel. 16 | 17 | More information in the [documentation](https://fstamour.gitlab.io/breeze/). 18 | 19 | ## Support me 20 | 21 | I'm doing this for fun, but if you find this useful or just want to 22 | cheer me up :) here's a link for that: 23 | 24 | Support me on Ko-Fi 25 | -------------------------------------------------------------------------------- /scratch-files/quicklisp.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:breeze.quicklisp) 2 | 3 | #++ 4 | (ql-dist:enabled-dists) 5 | ;; => (#) 6 | 7 | (defparameter *ql* (ql-dist:find-dist "quicklisp")) 8 | 9 | ;; Download everything, without installing it. 10 | (loop :for release :in (ql-dist:provided-releases *ql*) 11 | :do (ql-dist:ensure-local-archive-file release)) 12 | 13 | #++ 14 | (/ (length (ql-dist:installed-releases *ql*)) 15 | (length (ql-dist:provided-releases *ql*))) 16 | 17 | ;; Get the total size (in bytes) of all the archives 18 | ;; (require 'osicat) 19 | (loop :for release :in (ql-dist:installed-releases *ql*) 20 | :sum (osicat-posix:stat-size 21 | (osicat-posix:stat 22 | (ql-dist:local-archive-file release)))) 23 | ;; => 615869163 24 | 25 | ;; (float (/ 615869163 1024 1024)) 26 | ;; => 587.33 27 | -------------------------------------------------------------------------------- /src/range.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.range 2 | (:documentation "A simple class that holds a start and end points,") 3 | (:use #:cl #:breeze.generics) 4 | (:export #:range #:range= #:start #:end)) 5 | 6 | (in-package #:breeze.range) 7 | 8 | (defclass range () 9 | ((start 10 | :initarg :start 11 | :initform 0 12 | :accessor start) 13 | (end 14 | :initarg :end 15 | :initform nil 16 | :accessor end))) 17 | 18 | (defun range (start end) 19 | (make-instance 'range :start start :end end)) 20 | 21 | (defmethod print-object ((range range) stream) 22 | (print-unreadable-object 23 | (range stream :type t :identity nil) 24 | (format stream "~s-~s" (start range) (end range)))) 25 | 26 | (declaim (inline range=)) 27 | (defun range= (a b) 28 | (and (= (start a) (start b)) 29 | (= (end a) (end b)))) 30 | 31 | (defmethod eqv ((a range) (b range)) 32 | (range= a b)) 33 | -------------------------------------------------------------------------------- /docs/e_graphs.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 32155195-1bc4-4f2d-8f6a-12fb0bd68ecc 3 | :ROAM_ALIASES: egraphs 4 | :END: 5 | #+title: E-graphs 6 | 7 | * Introduction 8 | 9 | An e-graph is a data structure that can represent an exponential (or 10 | even infinite, because of loops) number of forms in a polynomial 11 | amount of space. 12 | 13 | They are a specific case of finite tree automata and are closely 14 | related to version-state algebras (VSAs). 15 | 16 | * In common lisp 17 | 18 | I have a proof of concept implementation in common lisp here: 19 | https://gitlab.com/fstamour/catchall/-/tree/master/egraph 20 | 21 | * References 22 | 23 | - https://egraphs-good.github.io/ 24 | - https://colab.research.google.com/drive/1tNOQijJqe5tw-Pk9iqd6HHb2abC5aRid?usp=sharing 25 | - https://arxiv.org/pdf/2004.03082.pdf 26 | - https://github.com/philzook58/awesome-egraphs 27 | 28 | ** TODO Find the paper relating FTA, VSAs and e-graphs 29 | -------------------------------------------------------------------------------- /scripts/emacs-director.patch: -------------------------------------------------------------------------------- 1 | diff --git a/director.el b/director.el 2 | index 6101320..b372278 100644 3 | --- a/director.el 4 | +++ b/director.el 5 | @@ -163,7 +163,7 @@ If DELAY-OVERRIDE is non-nil, the next step is delayed by that value rather than 6 | (director--log (format "FAILURE: %S" director--failure)) 7 | (run-with-timer director--delay nil 'director--end)) 8 | 9 | - ((length= director--steps 0) 10 | + ((= (length director--steps) 0) 11 | ;; Run after-step callback for last step 12 | (director--after-step) 13 | (run-with-timer (or delay-override director--delay) nil 'director--end)) 14 | @@ -216,7 +216,7 @@ If DELAY-OVERRIDE is non-nil, the next step is delayed by that value rather than 15 | 16 | (`(:suspend) 17 | nil) 18 | - 19 | + 20 | (`(:assert ,condition) 21 | (or (eval condition) 22 | (setq director--failure condition)) 23 | -------------------------------------------------------------------------------- /tests/channel.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | 3 | Those were smoke tests that I was running manually when developing 4 | this package. 5 | 6 | I could add actual test to this package... but I already have lots of 7 | tests on the commands which exercise this code _a lot_. 8 | 9 | |# 10 | 11 | (in-package #:breeze.channel) 12 | 13 | 14 | (cons nil nil) == (list nil) 15 | 16 | (let ((q (list nil))) 17 | (enqueue q 42) 18 | q) 19 | ((42) 42) 20 | 21 | (let ((q (list nil))) 22 | (enqueue q 42) 23 | (enqueue q 1) 24 | q) 25 | ((42 1) 42 1) 26 | 27 | (let ((q (list nil))) 28 | (enqueue q 42) 29 | (values (dequeue q) q)) 30 | 42 31 | (NIL) 32 | 33 | (let ((q (list nil))) 34 | (enqueue q 42) 35 | (enqueue q 1) 36 | (values (dequeue q) q)) 37 | 42 38 | ((1) 42 1) 39 | 40 | 41 | 42 | (let ((c (make-channel))) 43 | (cons (bt2:make-thread 44 | (lambda () (receive c))) 45 | (bt2:make-thread 46 | (lambda () (send c 42))))) 47 | -------------------------------------------------------------------------------- /scratch-files/xref-test.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (uiop:define-package #:breeze.xref.test 4 | (:documentation "Tests for breeze.xref.") 5 | (:mix #:breeze.xref #:cl #:alexandria) 6 | (:import-from #:breeze.test 7 | #:deftest 8 | #:is)) 9 | 10 | (in-package #:breeze.xref.test) 11 | 12 | (deftest test-calls-who 13 | (is (equalp '(dum:mul = is) (test-calls-who 'dum:mul)))) 14 | 15 | (deftest tested-by 16 | (is (member 'dum:mul (tested-by 'dum:mul))) 17 | (is (member 'dum:2x (tested-by 'dum:mul)))) 18 | 19 | (deftest test-case 20 | (is (equal '((dum:mul 2 6) (dum:mul 2 2)) (test-case 'dum:mul)))) 21 | 22 | 23 | (deftest package-test 24 | (is (equal '(dum:mul dum:2x) (package-test 'dum))) 25 | (is (equal (package-test 'dum) (package-test (find-package 'dum))))) 26 | 27 | (deftest calls-who 28 | (is (equalp (calls-who 'dum:2x) '(dum:mul)))) 29 | 30 | ;; (breeze.xref::function-without-test) 31 | -------------------------------------------------------------------------------- /tests/utils.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (uiop:define-package #:breeze.test.utils 4 | (:documentation "Tests for breeze.test.") 5 | (:mix #:cl #:alexandria #:breeze.utils) 6 | (:import-from #:parachute 7 | #:define-test 8 | #:define-test+run 9 | #:is 10 | #:true 11 | #:false)) 12 | 13 | (in-package #:breeze.test.utils) 14 | 15 | (define-test package-apropos) 16 | 17 | 18 | (define-test before-last 19 | (false (before-last '())) 20 | (false (before-last '(a))) 21 | (is eq 'a (before-last '(a b))) 22 | (is eq 'b (before-last '(a b c)))) 23 | 24 | #+nil 25 | (minimizing (x) 26 | (x 'a 10) 27 | (x 'b 5)) 28 | ;; => B, 5 29 | #+nil 30 | (minimizing (x) 31 | (x 'a nil)) 32 | #+nil 33 | (minimizing (x :tracep t) 34 | (x 'a 10)) 35 | 36 | (define-test length>1? 37 | (false (length>1? nil)) 38 | (false (length>1? '(a))) 39 | (true (length>1? '(a b)))) 40 | -------------------------------------------------------------------------------- /scratch-files/breeze+parachute.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package #:common-lisp-user) 3 | 4 | (defpackage #:breeze+parachute 5 | (:use :cl #:alexandria) 6 | (:import-from #:breeze.user 7 | #:current-packages) 8 | (:export 9 | #:run-all-tests)) 10 | 11 | (in-package #:breeze+parachute) 12 | 13 | (defun run-all-tests () 14 | (parachute:test (current-packages))) 15 | 16 | ;; (run-all-tests) 17 | 18 | ;; TODO less reporting, only report errors? 19 | (push (cons "run parachute tests" 20 | #'(lambda (string) 21 | (declare (ignore string)) 22 | (run-all-tests))) 23 | breeze.listener::*interactive-eval-hooks*) 24 | 25 | ;; Something to infer which package uses parachute 26 | #+nil 27 | (let ((packages (make-hash-table))) 28 | (loop :for symbol :being :the 29 | :symbol :of (find-package 'binstring.test) 30 | :do (setf (gethash (symbol-package symbol) packages) t)) 31 | (hash-table-keys packages)) 32 | -------------------------------------------------------------------------------- /scratch-files/bundle.lisp: -------------------------------------------------------------------------------- 1 | ;;; Trying to bundle breeze's source code in one big file 2 | #| 3 | 4 | See also: remote-loading.el and remote-loading.lisp 5 | 6 | Which makes me think: it would be nice to have some kind 7 | to "transactions" when loading common lisp code. To avoid half-loaded 8 | code. 9 | 10 | EDIT before I commit: I didn't have much time and didn't get far. BUT, 11 | I managed to figure out that (surprise surprise), the dependencies are 12 | hard to handle correctly... 13 | 14 | |# 15 | 16 | ;; Making sure the breeze system can be found. 17 | (asdf:locate-system "breeze") 18 | 19 | (defparameter *concat* 20 | (multiple-value-list 21 | (asdf:operate 'asdf:concatenate-source-op "breeze"))) 22 | 23 | (defparameter *bundle* 24 | (multiple-value-list 25 | (asdf:perform 'asdf:concatenate-source-op "breeze"))) 26 | 27 | (first *bundle*) 28 | #P"/home/fstamour/.cache/common-lisp/sbcl-2.4.0-linux-x64/home/fstamour/dev/breeze/src/breeze--system.lisp" 29 | 30 | (load (first *bundle*)) 31 | -------------------------------------------------------------------------------- /scripts/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to compile breeze and its tests in order to show 4 | # compilation errors and warnings 5 | # 6 | 7 | set -e 8 | 9 | cd "$(git rev-parse --show-toplevel)" 10 | 11 | # ASDF checks for warnings and errors when a file is compiled. The 12 | # variables asdf:*compile-file-warnings-behaviour* and 13 | # asdf:*compile-file-failure-behaviour* control the handling of any such 14 | # events. The valid values for these variables are :error, :warn, and 15 | # :ignore. 16 | 17 | # TODO this loads stuff twice... because I used ql:quickload to ensure 18 | # that the dependencies are downloaded before we compile again... 19 | 20 | set -x 21 | exec sbcl --noinform --non-interactive \ 22 | --eval "(asdf:load-asd (truename \"breeze.asd\"))" \ 23 | --eval "(ql:quickload '#:breeze/test :verbose t :explain t)" \ 24 | --eval "(setf asdf:*compile-file-warnings-behaviour* :error)" \ 25 | --eval "(asdf:compile-system '#:breeze/test :force t :verbose t)" 26 | -------------------------------------------------------------------------------- /scratch-files/unix-socket.el: -------------------------------------------------------------------------------- 1 | ;;;; I added domain (unix) socket to myelin (another project of mine), 2 | ;;;; I want to see if it would be hard to add them to breeze too. 3 | 4 | (defvar myelin-client nil 5 | "Client to myelin's domain socket") 6 | 7 | (setf myelin-client 8 | (make-network-process 9 | :name "myelin" 10 | :family 'local 11 | :remote (concat 12 | (xdg-runtime-dir) 13 | "/myelin/myelin.sock") 14 | :coding 'utf-8 15 | :filter (lambda (proc string) 16 | (message "Got %S from myelin (%S)" string proc)) 17 | ;; :buffer "*myelin*" 18 | )) 19 | 20 | (process-send-string myelin-client "hi") 21 | ;; => Got "hello" from myelin (#>) 22 | 23 | ;; Well, that was easy enough, but myelin's domain socket server is 24 | ;; currently implemented to receive one command and then close the 25 | ;; connection. It will probably harder to manage with breeze, 26 | ;; especially if the message get big. 27 | -------------------------------------------------------------------------------- /docs/slimv.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 35b3031a-bbd2-4f32-ba1f-7ec7fc268155 3 | :END: 4 | #+title: slimv 5 | 6 | https://github.com/kovisoft/slimv 7 | 8 | #+begin_quote 9 | Superior Lisp Interaction Mode for Vim ("SLIME for Vim") 10 | #+end_quote 11 | 12 | Interesting tid-bits from the readme: 13 | 14 | - Slimv is a SWANK client for Vim 15 | - Slimv comes with Paredit Mode 16 | - Slurpage and Barfage known from Emacs is also possible but in a 17 | different fashion: you don't move the list element in or out of the 18 | list, rather you move the opening or closing parenthesis over the 19 | element or sub-list. 20 | - It requires *with Python feature enabled* and *python* (must be the 21 | same Python version that was Vim compiled against) 22 | 23 | Tutorial: https://kovisoft.github.io/slimv-tutorial/tutorial.html 24 | 25 | * See also 26 | 27 | - [[id:54e6cd55-803b-4e15-82bc-a332130d020e][Sly/Slime integration]] 28 | - [[id:a7e572ba-a80b-44cf-9fd8-91f50307c675][breeze's eval v.s. sly's and slime's eval]] 29 | -------------------------------------------------------------------------------- /docs/breeze_on_the_internets.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: b9f7e1f4-dc86-46e0-860b-f845f180110e 3 | :END: 4 | #+title: Breeze on the internets 5 | 6 | Keeping track of public discussions about breeze. 7 | 8 | * Breeze on the internets 9 | 10 | ** Lisp project of the day 11 | 12 | https://40ants.com/lisp-project-of-the-day/2020/08/0166-breeze.html 13 | 14 | ** Reddit 15 | 16 | https://old.reddit.com/r/Common_Lisp/comments/pgtfm3/looking_for_feedbackhelp_on_a_project/ 17 | 18 | *** [[https://old.reddit.com/user/dzecniv][u/dzecniv]] 19 | 20 | > testing features along with workers and a file watcher? Shouldn't 21 | they be different projects? 22 | 23 | What annoys you when developing in lisp? 24 | 25 | I find that setting up a test framework is more difficult than it 26 | should be, so any effort on this area is appreciated. I mean: starting 27 | with 5am is ok (but could be easier with an editor command), running 28 | it from the CLI/a CI is less OK, getting the correct return code of 29 | the tests needs more work, etc. 30 | -------------------------------------------------------------------------------- /scratch-files/user-test.lisp: -------------------------------------------------------------------------------- 1 | 2 | (uiop:define-package #:breeze.user.test 3 | (:documentation "Tests for breeze.user.") 4 | (:mix #:breeze.user #:cl #:alexandria) 5 | (:import-from #:breeze.test 6 | #:deftest 7 | #:is)) 8 | 9 | (in-package #:breeze.user.test) 10 | 11 | (deftest current-packages 12 | (is (not (current-packages nil))) 13 | (is (equal (list *package*) (current-packages *package*))) 14 | (is (equal (current-packages 'breeze.user) (list (find-package :breeze.user)))) 15 | (is (equal (mapcar #'find-package '(breeze cl)) 16 | (current-packages '(breeze cl)))) 17 | (is (equal 18 | (list 19 | (find-package :cl)) 20 | (current-packages #'(lambda () 21 | (find-package :cl))))) 22 | (is 23 | (equal 24 | (sort 25 | (current-packages "breeze\\.(user|xref)\\.test") 26 | #'string< 27 | :key #'package-name) 28 | (list (find-package '#:breeze.user.test) 29 | (find-package '#:breeze.xref.test))))) 30 | 31 | ;; (main) 32 | ;; (next) 33 | -------------------------------------------------------------------------------- /docs/roadmap.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 11dd9906-75ff-4abc-82a5-b7dda0936f06 3 | :END: 4 | #+title: Roadmap 5 | 6 | * Roadmap 7 | 8 | This roadmap serves as a rule of thumb for priorisation. 9 | 10 | 1. Make the reader and linting incremental 11 | 2. Make a CLI that can be used 12 | - in the CI to generate a "code quality 13 | report" 14 | - from an editor for linting and fixing 15 | 3. Make commands more declarative (see [[file:src/pattern.lisp][pattern.lisp]]) 16 | 4. Improve the documentation 17 | 5. Implement better refactor commands 18 | 6. Improve breeze-eval function, add tests 19 | 7. Add support for other editors 20 | - this should help improving the internals 21 | 8. Reduce breeze's dependencies 22 | - break the "breeze" system in smaller systems, these could have 23 | more dependencies as needed 24 | - vendor alexandria 25 | - maybe vendor bordeaux-threads and/or chanl 26 | - this might not be necessary if breeze could be 100% out-of-image 27 | 28 | ** Status of the roadmap 29 | 30 | As of 2023-11-24, all the points from 1 through 5 are being worked on 31 | concurrently. 32 | -------------------------------------------------------------------------------- /scratch-files/try-context-menu-mode.el: -------------------------------------------------------------------------------- 1 | ;; https://ruzkuku.com/texts/emacs-mouse.html 2 | 3 | (when (fboundp 'context-menu-mode)) 4 | 5 | (defun breeze-context-menu (menu mouse-click-event) 6 | ;; (message "%S - %S" menu major-mode) 7 | (when (eq major-mode 'lisp-mode) 8 | (save-excursion 9 | (mouse-set-point mouse-click-event) 10 | (when 11 | ;; Add a separator at the end 12 | (define-key-after menu 13 | ;; Name of the separator 14 | [breeze-context-menu-separator] 15 | ;; Definition of the binding 16 | menu-bar-separator 17 | ;; After is nil, so the new binding goes at the end of the keymap 18 | )) 19 | (define-key-after menu [breeze-menu-test] 20 | '(menu-item "Breeze quickfix" 21 | breeze-quickfix 22 | :help "Call breeze quickfix at point")))) 23 | menu) 24 | 25 | define-key-after 26 | 27 | (add-hook 'context-menu-functions #'breeze-context-menu) 28 | 29 | 30 | ;; (context-menu-undo context-menu-region context-menu-middle-separator context-menu-local context-menu-minor) 31 | -------------------------------------------------------------------------------- /scratch-files/remote-loading.lisp: -------------------------------------------------------------------------------- 1 | ;;; Work in progress 2 | #| 3 | 4 | Goal: be able to load breeze into a remote lisp image 5 | 6 | Motivation: making breeze easier to use in more contexts 7 | 8 | Expected issue: Loading breeze might be easy... if we send the source 9 | code through sly/slime's listener. But it probably won't be easy to do 10 | so for all the dependencies... 11 | 12 | Also, I already tried asdf's "bundle" feature... It's not great, 13 | especially for third-party dependencies. 14 | 15 | From a high-level point of view, I can think of 3 major things to 16 | figure out: 17 | 18 | - how to load _one_ file 19 | - how to load a system 20 | - which systems to load? 21 | 22 | |# 23 | 24 | (defpackage #:breeze.remote-loading 25 | (:documentation "Helper code to load breeze into a lisp image that doesn't have 26 | breeze's code locally (i.e. a remote image).") 27 | (:use #:cl)) 28 | 29 | (in-package #:breeze.remote-loading) 30 | 31 | #++ (progn 32 | (asdf:already-loaded-systems) 33 | (asdf:system-defsystem-depends-on) 34 | 35 | (defclass fake-load-op (asdf:load-op) 36 | ()) 37 | 38 | (asdf:)) 39 | -------------------------------------------------------------------------------- /scripts/test-pedantic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used to run the tests with sbcl 4 | # 5 | 6 | set -e 7 | 8 | cd "$(git rev-parse --show-toplevel)" 9 | 10 | # ASDF checks for warnings and errors when a file is compiled. The 11 | # variables asdf:*compile-file-warnings-behaviour* and 12 | # asdf:*compile-file-failure-behaviour* control the handling of any such 13 | # events. The valid values for these variables are :error, :warn, and 14 | # :ignore. 15 | 16 | # TODO this loads stuff twice... because I used ql:quickload to ensure 17 | # that the dependencies are downloaded before we compile again... 18 | 19 | # TODO it would be nicer if the "lisp script" was in its own file 20 | exec sbcl --noinform --non-interactive \ 21 | --eval "(declaim (optimize (debug 3) (speed 0) (safety 3)))" \ 22 | --eval "(asdf:load-asd (truename \"breeze.asd\"))" \ 23 | --eval "(ql:quickload '#:breeze/test :verbose t)" \ 24 | --eval "(setf asdf:*compile-file-warnings-behaviour* :error)" \ 25 | --eval "(asdf:compile-system '#:breeze/test :force-not t :force t :verbose t)" \ 26 | --eval "(breeze.test.main:run-sbcl-tests :exitp t)" 27 | -------------------------------------------------------------------------------- /docs/neovim.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: f3a9c9a2-8180-43a8-9424-e66fd6190caa 3 | :END: 4 | #+title: Vim and Neovim integration 5 | 6 | NOT IMPLEMENTED, this is a design document (for the moment) 7 | 8 | * Syntax checking and linting in neovim 9 | :PROPERTIES: 10 | :ID: f8811c6f-9813-418f-a745-72be32add601 11 | :END: 12 | 13 | ** [[https://github.com/vim-syntastic/syntastic][syntastic]] - deprecated in september 2023, they suggests using [[https://github.com/dense-analysis/ale][ALE]] instead 14 | 15 | ** ALE - Asynchronous Lint Engine 16 | 17 | - AFAIK, it can either run an exectutable or connect to a language 18 | server. 19 | - async 20 | - also supports fixing (not just linting) using an executable 21 | - offers many more features than linting and fixing when using a 22 | laguage server. 23 | 24 | * Existing vim plugins for Common Lisp 25 | :PROPERTIES: 26 | :ID: f66155a2-d4fd-4aef-8336-8210cd472735 27 | :END: 28 | 29 | ** TODO vlime 30 | 31 | ** TODO slimv 32 | 33 | 34 | * Related notes 35 | 36 | - [[id:6bd2b06d-0a3c-4d32-9a1e-4f6f36e1003d][Emacs integration]] 37 | - [[id:086c7705-e5ec-4dc0-852d-211c055eb145][Visual Studio Code integration]] 38 | -------------------------------------------------------------------------------- /tests/pattern/rewrite.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:breeze.test.pattern) 2 | 3 | ;;; Match substitution 4 | 5 | (defun test-pattern-substitute (pattern bindings) 6 | (let ((compiled-pattern (compile-pattern pattern))) 7 | (pattern-substitute compiled-pattern bindings))) 8 | 9 | (define-test+run pattern-substitute 10 | (progn 11 | (is eq nil (test-pattern-substitute nil nil)) 12 | (is eq t (test-pattern-substitute t nil)) 13 | (is eq 'x (test-pattern-substitute 'x nil))) 14 | (progn 15 | (is eq nil (test-pattern-substitute nil t)) 16 | (is eq t (test-pattern-substitute t t)) 17 | (is eq 'x (test-pattern-substitute 'x t))) 18 | (progn 19 | (is eql 42 (test-pattern-substitute 20 | :?x (substitutions '((:?x 42))))) 21 | (is eq t (test-pattern-substitute t t)) 22 | (is eq 'x (test-pattern-substitute 'x t)))) 23 | 24 | 25 | 26 | ;;; Rules and rewrites 27 | 28 | 29 | #++ 30 | (let ((r (make-rewrite '(/ ?x ?x) 1))) 31 | (list (eqv (rewrite-pattern r) #(/ (var :?x) (var :?x))) 32 | (rewrite-template r))) 33 | 34 | #++ 35 | (make-rewrite '(/ (* ?x ?y) ?z) 36 | '(* ?x (/ ?y ?z))) 37 | 38 | #++ 39 | (make-rewrite '(/ ?x 1) ?x) 40 | -------------------------------------------------------------------------------- /docs/change_impact_analysis.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: f3e3952d-e6f7-4bb9-a85c-662ae82874eb 3 | :END: 4 | #+title: Change Impact Analysis 5 | 6 | I wanted to make breeze able to detect changes that haev impacts that 7 | are hard for beginners to keep in mind (and easy to forget even for 8 | experienced users). 9 | 10 | For example, you define a function, you rename it and re-evaluate the 11 | defun. The source code only has the new function, but the image has 12 | both the new and the old. It would be nice to have breeze help with 13 | perhaps =fmakunbound= it or to update the package's exports (both in 14 | the source and in the image). 15 | 16 | This is hard. 17 | 18 | But I just found a "keyword" that could help me find relevant 19 | techniques: [[https://en.wikipedia.org/wiki/Change_impact_analysis][Change Impact Analysis (Wikipedia)]]. 20 | 21 | Furthermore, I found this "change impact analysis" while looking at 22 | papers about "AST hashing", which is something I've had in my mind for 23 | practically a decade, if not more. I did _some_ experiments around 24 | this subject (AST hashing), but I was waiting for a better 25 | reader/parser before trying something more fancy (than hashing nested 26 | lists). 27 | -------------------------------------------------------------------------------- /scripts/better-trace.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.better-trace 2 | (:documentation "Configuring sbcl for more readable traces.") 3 | (:use #:cl)) 4 | 5 | (in-package #:breeze.better-trace) 6 | 7 | (defvar *default-trace-report-default* sb-debug:*trace-report-default*) 8 | 9 | ;; tracing is very very useful for debugging, but the default way sbcl 10 | ;; often prints "way too much" stuff 11 | (defun trace-report (depth function event stack-frame values) 12 | (declare (ignorable stack-frame)) 13 | ;; (pprint-logical-block stream values :prefix ... :suffix ...) 14 | (let ((*print-pretty* nil) 15 | (stream *standard-output*)) 16 | (terpri stream) 17 | (pprint-logical-block (stream values 18 | :per-line-prefix (format nil "~v@{~A~:*~}" depth " |")) 19 | ;; (loop :repeat depth :do (format stream " |")) 20 | (pprint-indent :current depth stream) 21 | (case event 22 | (:enter 23 | (format stream "~3d (~a ~{~a~^ ~})" depth function values)) 24 | (:exit 25 | (format stream "~3d => ~{~a~^, ~}" depth values)) 26 | (t 27 | (format stream "~3d ~s (~a ~{~a~^ ~})" depth event function values)))))) 28 | 29 | (setf sb-debug:*trace-report-default* 'trace-report) 30 | -------------------------------------------------------------------------------- /scratch-files/html.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | 3 | Playing around with the idea of replacing spinneret because it's pretty 4 | slow to load. 5 | 6 | |# 7 | 8 | (defpackage #:breeze.html 9 | (:documentation "") 10 | (:use #:cl)) 11 | 12 | (in-package #:breeze.html) 13 | 14 | (defclass tag () 15 | ((name :initform nil 16 | :initarg :name 17 | :accessor name) 18 | (attributes :initform nil 19 | :initarg :attributes 20 | :accessor attributes) 21 | (body :initform nil 22 | :initarg :body 23 | :accessor body))) 24 | 25 | (dolist (sym 26 | '(br 27 | a 28 | p 29 | div span 30 | pre 31 | html 32 | link 33 | ol li 34 | h1 h2 h3 h4 h5 h6)) 35 | (setf (symbol-value sym) sym) 36 | (export sym)) 37 | 38 | (defmethod make-tag (name attributes body) 39 | (values name attributes body)) 40 | 41 | (defun tag (name &rest attributes-and-body) 42 | (let ((attributes (butlast attributes-and-body)) 43 | (body (car (last attributes-and-body)))) 44 | (multiple-value-bind (name attributes body) 45 | (make-tag name attributes body)) 46 | (make-instance 'tag 47 | :name name 48 | :attributes attributes 49 | :body body))) 50 | 51 | (tag br) 52 | -------------------------------------------------------------------------------- /scratch-files/notes/function-redefinition.lisp: -------------------------------------------------------------------------------- 1 | 2 | (example "Change implementation" 3 | :before 4 | ((in-package 'examples) 5 | (defun 2x (x) 6 | (+ x x))) 7 | (:after 8 | ((in-package 'examples) 9 | (defun 2x (x) 10 | (* 2 x))))) 11 | 12 | (example "Add documentation" 13 | :before 14 | ((in-package 'examples) 15 | (defun 2x (x) 16 | (+ x x))) 17 | (:after 18 | ((in-package 'examples) 19 | (defun 2x (x) 20 | "Doubles x" 21 | (+ x x))))) 22 | 23 | (example "Change documentation" 24 | :before 25 | ((in-package 'examples) 26 | (defun 2x (x) 27 | "Doubles x" 28 | (+ x x))) 29 | (:after 30 | ((in-package 'examples) 31 | (defun 2x (x) 32 | "Adds x to itself" 33 | (+ x x))))) 34 | 35 | (example "Change implementation and documentation" 36 | :before 37 | ((in-package 'examples) 38 | (defun 2x (x) 39 | "Doubles x" 40 | (+ x x))) 41 | (:after 42 | ((in-package 'examples) 43 | (defun 2x (x) 44 | "Multiply x by 2" 45 | (* 2 x))))) 46 | 47 | (example "Add a function" 48 | :before 49 | ((in-package 'examples) 50 | (defun 2x (x) 51 | "Doubles x" 52 | (+ x x))) 53 | (:after 54 | ((in-package 'examples) 55 | (defun 2x (x) 56 | "Doubles x" 57 | (+ x x)) 58 | (defun 3x (x) 59 | "Multiply x by 3" 60 | (* 2 x))))) 61 | -------------------------------------------------------------------------------- /scripts/profile-loading.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-user) 2 | 3 | (defpackage #:breeze.profile-loading 4 | (:documentation "A script to figure out which dependency takes the most time to load 5 | when loading breeze.") 6 | (:use #:cl)) 7 | 8 | (in-package #:breeze.profile-loading) 9 | 10 | (defvar *system-load-times* (make-hash-table :test 'eql)) 11 | 12 | ;; Printing all the type of operations, to make sure I understand 13 | ;; asdf's operation order. 14 | (defmethod asdf:perform :before ((op t) (system asdf:system)) 15 | (format t "~&op: ~a system: ~a" op system)) 16 | 17 | ;; TODO We currently only check the time it takes to load a system, 18 | ;; not including the time it took to compile it. 19 | 20 | (defmethod asdf:perform :before ((op asdf:load-op) (system asdf:system)) 21 | (format t "op") 22 | (setf (gethash system *system-load-times*) (- (get-internal-run-time)))) 23 | 24 | (defmethod asdf:perform :after ((op asdf:load-op) (system asdf:system)) 25 | (incf (gethash system *system-load-times*) (get-internal-run-time))) 26 | 27 | (asdf:load-asd 28 | (merge-pathnames "../breeze.asd" *load-truename*)) 29 | 30 | ;; This doesn't work if some packages where loaded from a read-only 31 | ;; store, like guix or nix... 32 | (asdf:load-system "breeze" :force :all) 33 | 34 | 35 | ;; TODO Run this script in a container with sbcl and quicklisp set-up 36 | ;; TODO Analyze the results 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, Francis St-Amour 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/configuration.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.configuration 3 | (:documentation "Breeze's configuration") 4 | (:nicknames #:breeze.config) 5 | (:use #:cl) 6 | (:export 7 | #:*default-author* 8 | #:*default-system-licence* 9 | #:*capture-folder* 10 | #:*capture-template* 11 | #:load-config-file)) 12 | 13 | (in-package #:breeze.configuration) 14 | 15 | ;;; Configurations 16 | 17 | (defvar *default-author* "" 18 | "The default author when generating asdf system.") 19 | 20 | (defvar *default-system-licence* "Public" 21 | "The default licence when generating asdf system.") 22 | 23 | (defvar *capture-folder* 24 | (merge-pathnames "breeze-capture/" (user-homedir-pathname)) 25 | "The folder where to save capture files.") 26 | 27 | ;; TODO Load from /data/default-capture-template.lisp 28 | (defvar *capture-template* 29 | 30 | "(ql:quickload '(alexandria)) 31 | 32 | ;; make it easier to debug 33 | (declaim (optimize (speed 0) (safety 3) (debug 3))) 34 | 35 | #| 36 | 37 | Goal: 38 | 39 | Motivation: 40 | 41 | What am I going to try first: 42 | 43 | |# 44 | 45 | " 46 | "The format string used to populate a capture file when first creating it.") 47 | 48 | (defun load-config-file () 49 | "Load breeze's config file." 50 | (let ((path (uiop:xdg-config-home "breeze/config.lisp"))) 51 | (when (probe-file path) 52 | (let ((*package* (find-package '#:cl-user))) 53 | (load path))))) 54 | -------------------------------------------------------------------------------- /docs/index.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9c910250-abdc-4cbe-961b-46ad5c4f82d4 3 | :END: 4 | #+title: Breeze 5 | #+options: toc:nil 6 | 7 | * Documentation 8 | 9 | - [[id:d08ab932-1204-4e7c-9869-40fc53500071][Introduction]] 10 | - [[id:e5d64314-8b13-4a6b-997f-1aae94910d63][Goals and non-goals]] 11 | - [[id:11dd9906-75ff-4abc-82a5-b7dda0936f06][Roadmap]] 12 | - [[id:306350c9-0fb5-478b-958b-b35cae726280][Getting Started]] 13 | - [[id:5d211d9a-0749-4adb-abe0-e66133d09b5b][Editor integrations]] 14 | - [[id:14d42b3a-0a2f-4a3b-8937-7175e621c6ec][Design Decisions]] 15 | - [[id:279c4ea6-2004-4a7a-a2c9-905f27fae42c][Contributing]] 16 | - [[id:e712f3d1-0734-43f0-886a-3008ca5f722d][Testing Breeze]] 17 | - [[id:bb5c6ad4-0f89-48aa-9295-13e5e248a897][Glossary]] 18 | - [[file:reference.html][Reference]] 19 | - [[file:listing-breeze.html][Source listings]] 20 | 21 | * Internal notes 22 | 23 | - [[id:7d0f5cd2-d216-4882-84ac-27c004ad6fbd][Links to some dependencies' documentation]] 24 | - [[id:598a884c-56d0-4378-b5f5-acb2671d5112][Inbox]] 25 | - [[id:e2ff6189-1fd8-4d3c-9b7d-3d3ddbf2b0aa][Ideas]] 26 | - [[id:b9f7e1f4-dc86-46e0-860b-f845f180110e][Breeze on the internets]] 27 | - [[id:31236780-159e-4a58-9019-37f57f5b4997][FAQ from newbies about common lisp]] 28 | - [[id:62112623-6002-4cb9-87de-cb530ce0a36e][Silimar projects]] 29 | - [[id:13ea055e-4715-4583-811b-bff78ca300ee][Candidate Features]] 30 | 31 | ** More specific notes 32 | 33 | - [[id:32155195-1bc4-4f2d-8f6a-12fb0bd68ecc][E-graphs]] 34 | -------------------------------------------------------------------------------- /docs/programming_with_holes.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 69ab6084-2e41-4893-82b8-85ac04b1b1ca 3 | :END: 4 | #+title: Programming with holes 5 | 6 | * Programming with holes 7 | 8 | =Holes=, in programming, are something used to tell the language that 9 | a part of the program is incomplete. Some languages like Idris and 10 | Agda natively support =typed holes=. The way I see it, holes are used 11 | to falicitate the conversation between the programmer and the 12 | compiler. 13 | 14 | But, for languages like common lisp that doesn't support holes 15 | out-of-the box, how could we do that? In general, there are no symbol 16 | name that will never clash with other symbols, because symbols in 17 | common lisp can be any string. One idea is to use inline comments, 18 | like ~#| hole-name |#~. Breeze's parser would be able to recognize 19 | them and manipulate them. 20 | 21 | But what for? 22 | 23 | ** Snippets 24 | 25 | Holes can be used to both tell the user what he is expected to enter 26 | in a snippet and tell the editor where the user is expected to enter 27 | stuff. 28 | 29 | ** Typing 30 | 31 | A user could use a hole to tell the editor to infer the type of an 32 | expression or function and replace the hole by the appropriate 33 | declaration. 34 | 35 | ** Program synthesis 36 | 37 | A user could use a hole to tell the editor to find the right 38 | expression where the hole is. This probably requires that the user 39 | specify some more constraints, by giving types, writing tests, etc. 40 | -------------------------------------------------------------------------------- /src/cmds/quicklisp.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.quicklisp 2 | (:documentation "Utilities for quicklisp") 3 | (:use #:cl #:breeze.command) 4 | (:export #:quickload)) 5 | 6 | (in-package #:breeze.quicklisp) 7 | 8 | #+quicklisp 9 | (define-command quickload () 10 | "Choose a system to load with quickload." 11 | (let* ((systems (ql-dist:provided-systems t)) 12 | (mapping (make-hash-table :test 'equal)) 13 | (choices (mapcar (lambda (system) 14 | (let* ((release (ql-dist:release system)) 15 | (string (format nil "~a (~a ~a)" 16 | (ql-dist:name system) 17 | (ql-dist:prefix release) 18 | (ql-dist:short-description (ql-dist:dist release))))) 19 | (setf (gethash string mapping) system) 20 | string)) 21 | systems)) 22 | (choice (choose "Choose a system: " choices)) 23 | (chosen-system (gethash choice mapping))) 24 | (cond 25 | (chosen-system #| TODO |#) 26 | (t #| TODO |#)))) 27 | 28 | 29 | ;; TODO create a similar commands that uses asdf only 30 | ;; TODO create a similar command that calls (asdf:test-system) 31 | ;; TODO should extract the "choose a system" part 32 | ;; 33 | ;; other "quicklisp" commands: 34 | ;; - intstall ?? meh 35 | ;; - update client 36 | ;; - update dist 37 | ;; - check for update? 38 | -------------------------------------------------------------------------------- /docs/sly_slime_integration.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 54e6cd55-803b-4e15-82bc-a332130d020e 3 | :END: 4 | #+title: Sly/Slime integration 5 | 6 | * Other projects with slime/sly integration 7 | 8 | ** log4cl 9 | 10 | - https://github.com/sharplispers/log4cl/blob/master/log4cl.log4slime.asd 11 | - https://github.com/sharplispers/log4cl/blob/master/log4cl.log4sly.asd 12 | - https://github.com/sharplispers/log4cl/tree/master/elisp 13 | 14 | ** cepl 15 | 16 | - https://github.com/cbaggers/cepl/blob/master/docs/single-thread-swank.md 17 | - https://github.com/cbaggers/livesupport 18 | - https://github.com/cbaggers/swank.live 19 | 20 | ** cl-routes 21 | 22 | https://github.com/archimag/cl-routes/blob/master/src/routes-swank.lisp 23 | 24 | * TODO My old elisp snippet to eval with slime and kill the result 25 | 26 | https://gist.github.com/fstamour/2d7569beaf42c0a0883dc0ae559c6638 27 | 28 | #+begin_src emacs-lisp 29 | (defun slime-eval-save-output (string) 30 | "Evaluate STRING in Lisp and save the result in the kill ring." 31 | (slime-eval-async `(swank:eval-and-grab-output ,string) 32 | (lambda (result) 33 | (cl-destructuring-bind (output value) result 34 | (kill-new output) 35 | (message "Evaluation finished; pushed output to kill ring."))))) 36 | 37 | 38 | (defun lisp-eval-defun-in-kill-ring () 39 | (interactive) 40 | (slime-eval-save-output (slime-defun-at-point))) 41 | 42 | (global-set-key (kbd "C-M-z") 'lisp-eval-defun-in-kill-ring) 43 | #+end_src 44 | * TODO https://github.com/melisgl/mgl-pax for more emacs/slime integration :editor: 45 | -------------------------------------------------------------------------------- /scratch-files/edit-distance.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defun levenshtein (vec-a vec-b) 3 | (let* ((m (length vec-a)) 4 | (n (length vec-b)) 5 | (diff (make-array (list 2 (1+ n)) :element-type 'integer)) 6 | (p 0)) ;; p for pointer *shrug* 7 | 8 | (loop :for i :upto n :do 9 | (setf (aref diff 1 i) i)) 10 | (setf (aref diff 0 0) 1) 11 | 12 | (flet ((a (index) (aref vec-a (1- index))) 13 | (b (index) (aref vec-b (1- index))) 14 | (diff (i j) (aref diff i j)) 15 | (p (which) (if (zerop which) 16 | p (if (= 0 p) 1 0)))) 17 | (loop :for i :from 1 :upto m :do 18 | (loop :for j :from 1 :upto n 19 | :for cost = (if (eq (a i) (b j)) 0 1) ;; aka substitution-cost 20 | :do 21 | (setf (aref diff (p 0) j) 22 | (min 23 | (1+ (diff (p 1) j)) ;; deletion 24 | (1+ (diff (p 0) (1- j))) ;; insertion 25 | (+ cost (diff (p 1) (1- j))) ;; substitution 26 | ))) 27 | (when (/= m i) 28 | (setf p (if (zerop p) 1 0)) 29 | (setf (aref diff (p 0) 0) (1+ i)))) 30 | (diff (p 0) n)))) 31 | 32 | (levenshtein 33 | "ca" 34 | "abc") 35 | ;; => 3 36 | 37 | (levenshtein 38 | "string a" 39 | "string b") 40 | ;; => 1 41 | 42 | (levenshtein 43 | "string" 44 | "string") 45 | ;; => 0 46 | 47 | (levenshtein 48 | "a" 49 | "string") 50 | ;; => 6 51 | 52 | (levenshtein 53 | "string" 54 | "a") 55 | ;; => 6 56 | -------------------------------------------------------------------------------- /scripts/load-dependencies.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-user) 2 | 3 | (require '#:asdf) 4 | 5 | #-quicklisp 6 | (let ((quicklisp-init #P"/opt/quicklisp/setup.lisp")) 7 | (when (probe-file quicklisp-init) 8 | (load quicklisp-init))) 9 | 10 | (asdf:load-asd 11 | (merge-pathnames "../breeze.asd" *load-truename*)) 12 | 13 | (flet ((find-all-related-systems (system) 14 | "Given a system, find all systems defined in the same system definition 15 | file (including the one passed as argument)." 16 | (let ((result ()) 17 | (asd-pathname (asdf:system-source-file system))) 18 | (asdf:map-systems (lambda (system) 19 | (when (equal asd-pathname 20 | (asdf:system-source-file system)) 21 | (push system result)))) 22 | result))) 23 | (let* ((systems (find-all-related-systems "breeze")) 24 | (dependencies (remove-if (lambda (system-name) 25 | (uiop:string-prefix-p "breeze" system-name)) 26 | (loop 27 | :for system-name :in systems 28 | :for system = (asdf:find-system system-name) 29 | :for dependecy-list = (asdf:system-depends-on system) 30 | :append (copy-list dependecy-list))))) 31 | (ql:quickload dependencies) 32 | (mapcar #'asdf:register-immutable-system dependencies))) 33 | 34 | (uiop:dump-image "dependencies.core") 35 | -------------------------------------------------------------------------------- /tests/buffer.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.test.buffer 3 | (:documentation "Test package for #:breeze.buffer") 4 | (:use #:cl #:breeze.buffer) 5 | (:import-from #:parachute 6 | #:define-test 7 | #:define-test+run 8 | #:is 9 | #:true 10 | #:false 11 | #:of-type 12 | #:finish)) 13 | 14 | (in-package #:breeze.test.buffer) 15 | 16 | (define-test+run base 17 | (of-type 'buffer (make-buffer)) 18 | (is string= "#" 19 | (prin1-to-string (make-buffer))) 20 | (is string= "#" 21 | (prin1-to-string (make-buffer :name "foo.lisp"))) 22 | (is string= "#" 23 | (prin1-to-string (make-buffer :name "foo.lisp" 24 | :string "")))) 25 | 26 | (define-test+run current-package 27 | (false (let* ((string "(in-package #:cl-user)") 28 | (buffer (make-buffer :string string))) 29 | (setf (point buffer) 0) 30 | (current-package buffer))) 31 | (false (let* ((string "(in-package #:cl-user)") 32 | (buffer (make-buffer :string string))) 33 | (setf (point buffer) 10) 34 | (current-package buffer))) 35 | (is equalp '(:uninterned "CL-USER") 36 | (let* ((string "(in-package #:cl-user)") 37 | (buffer (make-buffer :string string))) 38 | (setf (point buffer) (length string)) 39 | (let (($node (current-package buffer))) 40 | (breeze.analysis:parse-symbol-node $node))))) 41 | -------------------------------------------------------------------------------- /scratch-files/xref.lisp: -------------------------------------------------------------------------------- 1 | 2 | 3 | (defun package-test (package) 4 | "Find all tests defined in PACKAGE." 5 | (let ((package (find-package package))) 6 | (loop 7 | :for test-name :being :the :hash-key :of breeze.test:*test* 8 | :using (hash-value test-definition) 9 | :for test-package = (second test-definition) 10 | :when (eq test-package package) 11 | :collect test-name))) 12 | 13 | (defun tests-by-package () 14 | "Return a hash-table of tests keyed by package." 15 | (cl-hash-util:collecting-hash-table (:mode :append) 16 | (loop 17 | :for test-name :being :the :hash-key :of breeze.test:*test* 18 | :using (hash-value test-definition) 19 | :for package = (second test-definition) 20 | :do (cl-hash-util:collect package test-definition)))) 21 | 22 | 23 | (defun calls-who (function-name) 24 | "Take a function name and returns a list of all the functions it calls." 25 | (uiop:while-collecting (collect) 26 | (walk-car 27 | (function-body function-name) 28 | (lambda (el) 29 | (when (function-body el) 30 | (collect el)))))) 31 | 32 | ;; TODO 33 | #+todo ;; It's done, but not tested nor used 34 | (defun list-function (&optional (package *package*)) 35 | "List all the functions, optionally filter by package" 36 | (loop :for function-name :being :the :hash-key :of *function* 37 | :when (if package 38 | (eq package (symbol-package function-name)) 39 | t) 40 | :collect function-name)) 41 | 42 | ;; TODO 43 | #+todo ;; Look at parse-smth in cover.lisp 44 | (defun function-without-documentation (&optional (package *package*))) 45 | -------------------------------------------------------------------------------- /scratch-files/analysis.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:breeze.test.analysis) 2 | 3 | ;; TODO move somewhere else 4 | ;; maybe add a file "debug utilities" 5 | (defmacro with-trace ((&rest spec) &body body) 6 | `(progn 7 | ,@(loop :for s :in spec :when (listp s) :collect `(trace ,@s)) 8 | (trace ,@(loop :for s :in spec :unless (listp s) :collect s)) 9 | (unwind-protect 10 | (progn ,@body) 11 | (untrace ,@(loop :for s :in spec 12 | :collect (if (listp s) (car s) s)))))) 13 | 14 | (with-trace ((match :methods t) 15 | breeze.pattern::go-down-together 16 | breeze.pattern::go-up-together) 17 | (match (compile-pattern '(if ?cond)) (parse "(if a)"))) 18 | 19 | (with-trace (eqv) 20 | (is eqv 21 | (substitutions `((?cond ,(iterator-value (token 4 5 :name "A"))))) 22 | (match (compile-pattern '(if ?cond)) (parse "(if a)") 23 | :skipp #'whitespace-or-comment-node-p))) 24 | 25 | (with-trace ((match :methods t) 26 | next 27 | breeze.pattern::go-down-together 28 | breeze.pattern::go-up-together) 29 | (match (compile-pattern '(if (:maybe ?cond))) (parse "(if)"))) 30 | 31 | (with-trace (match ;;(match :methods t) 32 | breeze.pattern::go-down-together 33 | breeze.pattern::go-up-together) 34 | (test-malformed-if-node-p "(if a b c d e)")) 35 | 36 | 37 | (with-trace (match ;;(match :methods t) 38 | breeze.pattern::go-down-together 39 | breeze.pattern::go-up-together) 40 | (test-malformed-if-node-p "(if a b)")) 41 | 42 | (with-trace ((match :methods t)) 43 | (match (compile-pattern '(if ?cond)) (parse "(if)"))) 44 | -------------------------------------------------------------------------------- /tests/package-commands.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.test.package-commands 2 | (:documentation "Tests for the package breeze.package-commands") 3 | (:use #:cl #:breeze.package-commands) 4 | (:import-from #:parachute 5 | #:define-test 6 | #:define-test+run 7 | #:is 8 | #:true 9 | #:false 10 | #:of-type 11 | #:finish)) 12 | 13 | (in-package #:breeze.test.package-commands) 14 | 15 | ;; TODO Variants: *insert-defpackage/cl-user-prefix* 16 | ;; TODO infer-project-name 17 | ;; TODO infer-is-test-file 18 | ;; TODO infer-package-name-from-file 19 | #++ 20 | (define-test+run insert-defpackage 21 | (let* ((trace (drive-command #'insert-defpackage 22 | :inputs '("pkg") 23 | :context '()))) 24 | 25 | (common-trace-asserts 'insert-defpackage trace 4) 26 | (destructuring-bind (input request) (first trace) 27 | (false input) 28 | (is string= "read-string" (first request)) 29 | (is string= "Name of the package: " (second request)) 30 | (false (third request))) 31 | (destructuring-bind (input request) (second trace) 32 | (is string= "pkg" input) 33 | (is string= "insert" (first request)) 34 | (is equal "(defpackage " (second request))) 35 | (destructuring-bind (input request) (third trace) 36 | (false input) 37 | (is string= "insert" (first request)) 38 | (is equal 39 | '("#:pkg" 40 | " (:documentation \"\")" 41 | " (:use #:cl))" 42 | "" 43 | "(in-package #:pkg)") 44 | (split-by-newline (second request)))))) 45 | -------------------------------------------------------------------------------- /scratch-files/macroexpand.lisp: -------------------------------------------------------------------------------- 1 | ;;;; Goal: experimenting with macroexpand-hook to see if it could be 2 | ;;;; of use (e.g. to detect a `defun`). 3 | 4 | (cl:in-package #:common-lisp-user) 5 | 6 | (defpackage #:macroexpand 7 | (:use :cl)) 8 | 9 | (in-package #:macroexpand) 10 | 11 | (describe '*macroexpand-hook*) 12 | #| 13 | Documentation: 14 | The value of this variable must be a designator for a function that can 15 | take three arguments, 16 | - a macro expander function, 17 | - the macro form to be expanded, and 18 | - the lexical environment to expand in. 19 | 20 | The function should return the expanded form. 21 | This function is called by MACROEXPAND-1 whenever a *runtime* expansion 22 | is needed. 23 | 24 | Initially this is set to FUNCALL. 25 | |# 26 | 27 | 28 | (defmacro defun* (name (&rest lambda-list) &body body) 29 | `(progn 30 | (format t "~&defun* ~a" name) 31 | (defun ,name ,lambda-list ,@body))) 32 | 33 | (defun macroexpand-hook (expander macro-form environment) 34 | (let ((macroexpanded-form 35 | (funcall expander macro-form environment))) 36 | (format t "~&The macro-form ~%~a~%~%expanded to ~%~a" 37 | macro-form 38 | macroexpanded-form))) 39 | 40 | (setf *macroexpand-hook* #'macroexpand-hook) 41 | 42 | 43 | (defun* x2 (x) 44 | (* x 2)) 45 | 46 | #| 47 | The macro-form 48 | (DEFUN* X2 49 | (X) 50 | (* X 2)) 51 | 52 | expanded to 53 | (PROGN (FORMAT T ~&defun* ~a NAME) (DEFUN X2 (X) (* X 2)))NIL 54 | |# 55 | 56 | ;;; So I would need a kind of code-walker to find "defuns" in the 57 | ;;; macro-expanded code. 58 | ;;; 59 | ;;; TODO Take a look at cl-walker 60 | 61 | (ql:quickload 'cl-walker) 62 | 63 | ;;; Note: intercepting and analyzing _every_ forms might slow down 64 | ;;; stuff... 65 | -------------------------------------------------------------------------------- /scratch-files/fsa.lisp: -------------------------------------------------------------------------------- 1 | ;;;; FSA - Finite State Automaton 2 | 3 | (defpackage #:breeze.fsa 4 | (:documentation "") 5 | (:use #:cl)) 6 | 7 | (in-package #:breeze.fsa) 8 | 9 | ;; field = commutativity, associativity and distribution of the 2 operations... 10 | 11 | (let* ((classes #(+ a b)) 12 | (initial-states #(0)) 13 | (occurrences '((a . 2) (b . 1))) 14 | (input #(+ a b a)) 15 | (transitions '((0 . (1 2)) 16 | (1 . (1 2)) 17 | (2 . (1 2))))) 18 | (loop 19 | :with counts = (alexandria:alist-hash-table occurrences) 20 | :for state = (aref initial-states 0) 21 | :then (cdr (assoc class transitions)) 22 | :for guard :below 100 23 | :for item :across input 24 | :for class = (position item classes) 25 | :do 26 | (unless (if (listp state) 27 | (find class state) 28 | (= class state)) 29 | (return nil)) 30 | (alexandria:when-let ((count (gethash item counts))) 31 | (when (zerop count) 32 | (return (values nil (format nil "Too many ~s's" item)))) 33 | (decf (gethash item counts))) 34 | :do (print (list item state (alexandria:hash-table-alist counts))) 35 | :finally (progn 36 | (maphash (lambda (k v) 37 | (when (plusp v) 38 | (return (values nil (format nil "Not enough ~s's" k))))) 39 | counts) 40 | (return t)))) 41 | 42 | 43 | (flet ((h (x) 44 | (loop :for item :across x 45 | :sum (sxhash item)))) 46 | (apply #'= 47 | (mapcar #'h 48 | '(#(+ a b a) 49 | #(+ a a b) 50 | #(+ b a a))))) 51 | -------------------------------------------------------------------------------- /scratch-files/function-fingerprinting.lisp: -------------------------------------------------------------------------------- 1 | ;;;; 2 march 2020 - Trying to fingerprint functions by generating 2 | ;;;; randomly, but deterministically a bunch of input and hashing all 3 | ;;;; the outputs. 4 | 5 | (in-package :breeze.user) 6 | 7 | (ql:quickload '(random-state ironclad flexi-streams)) 8 | 9 | ;;; A Bunch of simple functions 10 | (defun x2 (x) (* x 2)) 11 | (defun double (x) (+ x x)) 12 | (defun square (x) (* x x)) 13 | (defun pow2 (x) (expt x 2)) 14 | 15 | 16 | 17 | (defvar *seed* 42) 18 | 19 | (defun repr (x) 20 | "Convert anything to something that can be digested (hashed)." 21 | (flexi-streams:string-to-octets (format nil "~s~s" (type-of x) x))) 22 | 23 | ;; (repr 42) 24 | ;; (repr "42") 25 | 26 | #| 27 | Assumes that the function takes 1 32-bits argument 28 | Could change 29 | * the seed 30 | * the random number generator 31 | * the number of number generated 32 | * the digest algorithm 33 | * the digest size 34 | |# 35 | 36 | (defun fingerprint-function (fn) 37 | "" 38 | (let ((rnd (random-state:make-generator :mersenne-twister-32 *seed*)) 39 | (max (1- (expt 2 31))) 40 | (digester (ironclad:make-digest :shake256 :output-length 4))) 41 | (flet ((gen-int () (random-state:random-int rnd (- max) max))) 42 | (loop :for i :below 10000 43 | :for x = (funcall fn (gen-int)) 44 | :do (ironclad:update-digest digester (repr x))) 45 | (ironclad:produce-digest digester)))) 46 | 47 | 48 | 49 | (fingerprint-function #'x2) 50 | ;; => #(207 80 46 67) 51 | 52 | (fingerprint-function #'double) 53 | ;; => #(207 80 46 67) 54 | 55 | (fingerprint-function #'square) 56 | ;; => #(95 88 2 241) 57 | 58 | (fingerprint-function #'pow2) 59 | ;; => #(95 88 2 241) 60 | 61 | 62 | 63 | ;;; 64 | ;;; Variant: generate a digest for every input and keep the N smallests 65 | ;;; very much like Min-Hash 66 | ;;; 67 | -------------------------------------------------------------------------------- /scratch-files/notes/error-system-loadedp.txt: -------------------------------------------------------------------------------- 1 | There is no applicable method for the generic function 2 | # 3 | when called with arguments 4 | (#P"/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.asd"). 5 | [Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR] 6 | See also: 7 | Common Lisp Hyperspec, 7.6.6 [:section] 8 | 9 | Restarts: 10 | 0: [RETRY] Retry calling the generic function. 11 | 1: [ABORT] abort thread (#) 12 | 13 | Backtrace: 14 | 0: ((:METHOD NO-APPLICABLE-METHOD (T)) # #P"/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.asd") [fast-method] 15 | 1: (SB-PCL::CALL-NO-APPLICABLE-METHOD # (#P"/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.asd")) 16 | 2: ((LABELS ASDF/COMPONENT::RECURSE :IN ASDF/COMPONENT:SUB-COMPONENTS) #P"/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.asd") 17 | 3: (ASDF/COMPONENT:SUB-COMPONENTS #P"/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.asd" :TYPE T) 18 | 4: (BREEZE.ASDF:LOADEDP "/home/fstamour/quicklisp/local-projects/sb-unix-socket/sb-unix-socket.lisp") 19 | 5: (BREEZE.REFACTOR::MAYBE-ASK-TO-LOAD-SYSTEM) 20 | 6: ((LAMBDA NIL :IN BREEZE.REFACTOR:QUICKFIX)) 21 | 7: (BREEZE.COMMAND::CALL-WITH-COMMAND-SIGNAL-HANDLER #) 22 | 8: (BREEZE.COMMAND::CANCEL-COMMAND-ON-ERROR 2 #) 23 | 9: ((LABELS BORDEAUX-THREADS::%BINDING-DEFAULT-SPECIALS-WRAPPER :IN BORDEAUX-THREADS::BINDING-DEFAULT-SPECIALS)) 24 | -------------------------------------------------------------------------------- /githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | 9 | # Stop on first error 10 | set -e 11 | 12 | echo "Executing pre-commit hook" 13 | 14 | if git rev-parse --verify HEAD >/dev/null 2>&1 15 | then 16 | against=HEAD 17 | else 18 | # Initial commit: diff against an empty tree object 19 | against=$(git hash-object -t tree /dev/null) 20 | fi 21 | 22 | # If you want to allow non-ASCII filenames set this variable to true. 23 | allownonascii=true 24 | 25 | # Redirect output to stderr. 26 | exec 1>&2 27 | 28 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 29 | # them from being added to the repository. We exploit the fact that the 30 | # printable range starts at the space character and ends with tilde. 31 | if [ "$allownonascii" != "true" ] && 32 | # Note that the use of brackets around a tr range is ok here, (it's 33 | # even required, for portability to Solaris 10's /usr/bin/tr), since 34 | # the square bracket bytes happen to fall in the designated range. 35 | test $(git diff --cached --name-only --diff-filter=A -z $against | 36 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 37 | then 38 | cat <<\EOF 39 | Error: Attempt to add a non-ASCII file name. 40 | 41 | This can cause problems if you want to work with people on other platforms. 42 | 43 | To be portable it is advisable to rename the file. 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | git diff-index --check --cached $against -- 50 | 51 | 52 | # run the tests 53 | ./scripts/test.sh 54 | 55 | # generate the docs 56 | ./scripts/doc.sh 57 | 58 | echo "Pre-commit hook done" 59 | -------------------------------------------------------------------------------- /src/cmds/+quickproject.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze+quickproject 2 | (:documentation "Integration with quickproject.") 3 | (:use #:cl) 4 | (:import-from #:breeze.command 5 | #:define-command 6 | #:read-string 7 | #:find-file 8 | #:message) 9 | (:import-from #:breeze.project 10 | #:choose-local-project-directories 11 | #:confirm-scaffold-directory) 12 | (:import-from #:breeze.config 13 | #:*default-author* 14 | #:*default-system-licence*) 15 | (:export #:breeze-quickproject)) 16 | 17 | (in-package #:breeze+quickproject) 18 | 19 | (define-command breeze-quickproject (project-name directory) 20 | "Create a project interactively using quickproject." 21 | (let* (;; TODO Currently the user is able to enter an empty string 22 | (project-name 23 | (or project-name 24 | (read-string "Name of the project: "))) 25 | (directory 26 | (confirm-scaffold-directory 27 | (uiop:ensure-directory-pathname 28 | (or directory 29 | (merge-pathnames project-name 30 | (choose-local-project-directories)))))) 31 | (author (read-string "Author of the project: " 32 | *default-author*)) 33 | (license (read-string "Licence of the project: " 34 | *default-system-licence*))) 35 | ;; TODO depends-on 36 | ;; TODO include-copyright 37 | ;; TODO template-directory 38 | ;; TODO template-parameters 39 | ;; TODO add validations 40 | (quickproject:make-project 41 | directory 42 | :name project-name 43 | :author author 44 | :license license) 45 | (message "Project \"~a\" created." directory) 46 | (find-file directory))) 47 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Only run pipelines for merge requests, tags, and protected branches. 2 | workflow: 3 | rules: 4 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 5 | - if: $CI_COMMIT_TAG 6 | - if: $CI_COMMIT_REF_PROTECTED == "true" 7 | 8 | include: 9 | - local: .gitlab/test-job.gitlab-ci.yml 10 | inputs: 11 | lisp-impl: sbcl 12 | # disabled: 13 | # 1. abcl fails to load asdf in the container... 14 | # 2. needs to push the container to gitlab 15 | # - local: .gitlab/test-job.gitlab-ci.yml 16 | # inputs: 17 | # lisp-impl: abcl 18 | # disable: 19 | # 1. need to add scripts/manifest-ccl.scm 20 | # 2. need to push the image to gitlab 21 | # - local: .gitlab/test-job.gitlab-ci.yml 22 | # inputs: 23 | # lisp-impl: ccl 24 | # disabled: need to fix 5 tests that are not portable (i.e. they 25 | # return things in a different order in sbcl and ecl) 26 | # - local: .gitlab/test-job.gitlab-ci.yml 27 | # inputs: 28 | # lisp-impl: ecl 29 | # disabled: 30 | # 1. need to add scripts/test-clasp.sh 31 | # 2. need to add scripts/manifest-clasp.scm 32 | # 3. need to push the container to gitlab 33 | # - local: .gitlab/test-job.gitlab-ci.yml 34 | # inputs: 35 | # lisp-impl: clasp 36 | 37 | # Build public/ folder using org-publish on docs/ 38 | doc: 39 | image: docker:24.0.7 40 | services: 41 | - docker:24.0.5-dind 42 | script: 43 | - apk add --no-cache make bash 44 | - make public 45 | artifacts: 46 | paths: 47 | - public 48 | 49 | pages: 50 | needs: 51 | - job: doc 52 | artifacts: true 53 | script: 54 | - echo "nothing to do!" 55 | rules: 56 | # automatically run on the default branch 57 | - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH 58 | # otherwise, the job must be run manually 59 | - when: manual 60 | artifacts: 61 | paths: 62 | - public 63 | -------------------------------------------------------------------------------- /src/cmds/package-commands.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.package-commands 2 | (:documentation "Package-related commands") 3 | (:use #:cl 4 | #:breeze.analysis 5 | #:breeze.command 6 | #:breeze.package 7 | #:breeze.workspace) 8 | (:export #:insert-defpackage 9 | #:insert-local-nicknames 10 | #:insert-in-package-cl-user)) 11 | 12 | (in-package #:breeze.package-commands) 13 | 14 | 15 | ;;; Commands for inserting and modifying package-related forms 16 | 17 | (define-command insert-defpackage () 18 | "Insert a defpackage form." 19 | (declare (context :top-level)) 20 | (let ((package-name 21 | (read-string 22 | "Name of the package: " 23 | (infer-package-name-from-file (current-buffer-filename))))) 24 | (when (in-package-cl-user-p) 25 | (insert 26 | "(cl:in-package #:cl-user)~%~%")) 27 | (progn 28 | ;; TODO if nil (insert "(uiop:define-package ") 29 | (insert "(defpackage ")) 30 | ;; TODO don't insert the (in-package ...) if it already exists 31 | ;; TODO add documentation "Tests for the package X" 32 | (insert 33 | "#:~a~ 34 | ~% (:documentation \"\")~ 35 | ~% (:use #:cl))~ 36 | ~%~ 37 | ~%(in-package #:~a)" 38 | package-name package-name))) 39 | 40 | (define-command insert-local-nicknames () 41 | "Insert local nicknames." 42 | (declare (context (:child-of :package-definition))) ; TODO 43 | (insert 44 | "(:local-nicknames ~{~a~^~%~})" 45 | (loop :for name = (read-string "Name of the package to alias: ") 46 | :while (plusp (length name)) 47 | :for alias = (read-string "Alias of the package: ") 48 | :collect (format nil "(#:~a #:~a)" alias name)))) 49 | 50 | (define-command insert-in-package-cl-user () 51 | "Insert ~(cl:in-package #:cl-user)~." 52 | (declare (context :top-level)) 53 | (insert "(cl:in-package #:cl-user)")) 54 | -------------------------------------------------------------------------------- /src/logging.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.logging 2 | (:documentation "Utilities for logging.") 3 | (:use #:cl) 4 | (:export 5 | #:log-level 6 | #:log-message 7 | #:log-critical 8 | #:log-error 9 | #:log-warning 10 | #:log-info 11 | #:log-debug)) 12 | 13 | (in-package #:breeze.logging) 14 | 15 | (defparameter *log-level* :info 16 | "The current log level") 17 | 18 | (defun log-level () "Get the current log level." *log-level*) 19 | 20 | (defun (setf log-level) (new-value) 21 | (check-type new-value (member :critical :error :warning :info :debug)) 22 | (setf *log-level* new-value)) 23 | 24 | (defun log-stream () 25 | "Get the current log output stream." 26 | *trace-output*) 27 | 28 | (defun compare-level (cmp level1 level2) 29 | "Compare two log levels." 30 | ;; It's implemented in a way to have a total order, without actually 31 | ;; assigning a value to each level. 32 | (funcall cmp 33 | (length (member level1 #1='(:critical :error :warning :info :debug))) 34 | (length (member level2 #1#)))) 35 | 36 | (defun log-message (level control-string &rest args) 37 | (let ((current-level (log-level)) 38 | (stream (log-stream))) 39 | (when (compare-level #'>= level current-level) 40 | ;; TODO maybe print the time too? 41 | (format stream "~&~a ~?~%" level control-string args)))) 42 | 43 | 44 | (macrolet ((def (level) 45 | `(defun ,(alexandria:symbolicate 'log- level) (control-string &rest args) 46 | (apply #'log-message ,level control-string args)))) 47 | (def :critical) 48 | (def :error) 49 | (def :warning) 50 | (def :info) 51 | (def :debug)) 52 | 53 | ;; To manually test if the log level is respected. 54 | #++ 55 | (progn 56 | (format (log-stream) "~%~%Current log level: ~s~%~%" (log-level)) 57 | (log-debug "debug") 58 | (log-info "info") 59 | (log-warning "warn") 60 | (log-error "err") 61 | (log-critical "crit")) 62 | -------------------------------------------------------------------------------- /docs/getting_started.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 306350c9-0fb5-478b-958b-b35cae726280 3 | :END: 4 | #+title: Getting Started 5 | 6 | * Clone 7 | 8 | Clone this repository, for example, in quicklisp's local-projects 9 | folder. 10 | 11 | #+begin_src shell 12 | git clone --recursive git@codeberg.org:fstamour/breeze.git ~/quicklisp/local-projects/breeze 13 | #+end_src 14 | 15 | * Getting started with Emacs 16 | :PROPERTIES: 17 | :ID: 3976965c-cb83-4901-9587-3897cc207682 18 | :END: 19 | 20 | ** Load in emacs 21 | 22 | #+begin_src emacs-lisp 23 | (add-to-list 'load-path "~/quicklisp/local-projects/breeze/emacs/") 24 | (require 'breeze) 25 | #+end_src 26 | 27 | ** Configure emacs to use breeze-minor-mode 28 | 29 | Add a hook to enable breeze's minor mode in =lisp-mode= automatically: 30 | 31 | The mode will add some bindings (most notably =C-.=, which is bound to 32 | the command =breeze-quickfix=). 33 | 34 | #+begin_src emacs-lisp 35 | (add-hook 'lisp-mode-hook #'breeze-minor-mode) 36 | #+end_src 37 | 38 | Additionally, breeze can be used as a "on-the-fly" linter for common 39 | lisp source files by enabling flymake and configuring flymake to use 40 | breeze. 41 | 42 | #+begin_src emacs-lisp 43 | ;; Ensure breeze is loaded when a sly or slime connection is opened 44 | (breeze-enable-connected-hook) 45 | 46 | ;; Enable breeze-minor-mode in lisp-mode 47 | (breeze-enable-minor-mode-hook) 48 | 49 | ;; Enable flymake-mode in breeze-minor-mode 50 | (breeze-minor-mode-enable-flymake-mode) 51 | 52 | ;; Configure eldoc to show both documentation and flymake's messages 53 | ;; See https://www.masteringemacs.org/article/seamlessly-merge-multiple-documentation-sources-eldoc for a fancier solution 54 | (setf eldoc-documentation-strategy 'eldoc-documentation-compose) 55 | #+end_src 56 | 57 | * See also 58 | 59 | - [[id:279c4ea6-2004-4a7a-a2c9-905f27fae42c][Contributing]] 60 | - [[id:bb5c6ad4-0f89-48aa-9295-13e5e248a897][Glossary]] 61 | - [[id:31236780-159e-4a58-9019-37f57f5b4997][FAQ from newbies about common lisp]] 62 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | # When to trigger this workflow 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: ${{ matrix.os }} 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - macos-latest 18 | - ubuntu-latest 19 | - windows-latest 20 | 21 | # run the job on every combination of "os" above 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - name: "Windows: Install sbcl" 26 | if: matrix.os == 'windows-latest' 27 | run: | 28 | echo "C:\\Program Files\\Steel Bank Common Lisp\\2.0.0\\" >> $env:GITHUB_PATH 29 | cat $env:GITHUB_PATH 30 | echo "SBCL_HOME=C:\\Program Files\\Steel Bank Common Lisp\\2.0.0\\" >> $env:GITHUB_ENV 31 | cat $env:GITHUB_ENV 32 | curl -L http://downloads.sourceforge.net/project/sbcl/sbcl/2.0.0/sbcl-2.0.0-x86-64-windows-binary.msi --output sbcl.msi 33 | msiexec /qn /i sbcl.msi 34 | 35 | - name: "Ubuntu: Install sbcl" 36 | if: matrix.os == 'ubuntu-latest' 37 | run: | 38 | sudo apt-get update 39 | sudo apt-get install -y sbcl 40 | 41 | - name: "MacOs: Install sbcl" 42 | if: matrix.os == 'macos-latest' 43 | run: | 44 | brew install sbcl 45 | 46 | - name: "Print sbcl version" 47 | run: sbcl --version 48 | 49 | - name: Checkout repository 50 | uses: actions/checkout@v3 51 | 52 | - name: Setup quicklisp 53 | run: | 54 | curl -kLO https://beta.quicklisp.org/quicklisp.lisp 55 | printenv | sort 56 | sbcl --noinform --non-interactive --load scripts/setup-quicklisp.lisp 57 | 58 | - name: Tests 59 | shell: bash 60 | run: scripts/test-sbcl.sh 61 | 62 | # - name: 'Upload Artifact' 63 | # uses: actions/upload-artifact@v3 64 | # with: 65 | # name: docs 66 | # path: docs/ 67 | -------------------------------------------------------------------------------- /src/package.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.package 3 | (:documentation "Package utilities") 4 | (:use #:cl #:breeze.analysis) 5 | (:import-from #:alexandria 6 | #:when-let 7 | #:when-let*) 8 | (:export #:in-package-node-p 9 | ;; re-export from breeze.analysis 10 | #:map-top-level-in-package)) 11 | 12 | (in-package #:breeze.package) 13 | 14 | ;; TODO I want to check if a node is an "in-package" node... 15 | ;; - [ ] case converting if necessary 16 | ;; - [x] skip whitespaces 17 | ;; - [x] check if there's a package designator 18 | ;; 19 | ;; Now, I have a chicken-and-egg issue because of 20 | ;; package-local-nicknames... I need to know what is the current 21 | ;; package to look for PLNs to find the in-pacakge form, but I need 22 | ;; the in-package to know the current package. 23 | 24 | (define-node-matcher in-package-node-p ((in-package ?package-designator)) 25 | (unless (quotedp node-iterator) 26 | (when ?package-designator 27 | (let ((package-designator-node (value ?package-designator))) 28 | (when (or (token-node-p package-designator-node) 29 | (string-node-p package-designator-node) 30 | (sharp-uninterned-node-p package-designator-node)) 31 | ;; TODO else... it's a malformed in-package form 32 | ?package-designator))))) 33 | 34 | ;; TODO add tests 35 | (defmethod map-top-level-in-package (function (state state)) 36 | "Map FUNCTION over all top-level (in-package ...) forms in STATE." 37 | (map-top-level-forms 38 | (lambda (node-iterator) 39 | (when-let ((package-name (in-package-node-p node-iterator))) 40 | (funcall function node-iterator package-name))) 41 | state)) 42 | 43 | ;; TODO add tests 44 | (defmethod locate-package-definition ((package package) #| TODO haystack |#) 45 | (locate-package-definition (package-name package))) 46 | 47 | ;; TODO add tests 48 | (defmethod locate-package-definition ((package node-iterator) #| TODO haystack |#) 49 | (locate-package-definition (node-string-designator package))) 50 | -------------------------------------------------------------------------------- /tests/package.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.test.package 3 | (:documentation "Tests for the package breeze.package") 4 | (:use #:cl #:breeze.package #:breeze.analysis) 5 | (:import-from #:parachute 6 | #:define-test 7 | #:define-test+run 8 | #:is 9 | #:true 10 | #:false 11 | #:of-type 12 | #:finish)) 13 | 14 | (in-package #:breeze.test.package) 15 | 16 | #++ ;; Sanity-check 17 | (mapcar #'read-from-string 18 | '("in-package" 19 | "common-lisp:in-package" 20 | "cl:in-package" 21 | "cl-user::in-package" 22 | "common-lisp-user::in-package")) 23 | 24 | (defun test-in-package-node-p (string) 25 | ;; The funky reader macro and quasiquote is to fuck with slime and 26 | ;; sly's regex-based search for "(in-package". Without this the 27 | ;; rest of the file is evaluated in cl-user by slime and sly. 28 | (let ((package-designator-node 29 | #.`(,'in-package-node-p (make-node-iterator string)))) 30 | (when package-designator-node 31 | (node-string package-designator-node)))) 32 | 33 | ;; TODO (in-package #+x 'a #-x 'b) 34 | (define-test+run in-package-node-p 35 | (is equal "x" (test-in-package-node-p "(in-package x)")) 36 | (is equal nil (test-in-package-node-p "(in-package #)")) 37 | (is equal ":x" (test-in-package-node-p "(in-package :x)")) 38 | (is equal "#:x" (test-in-package-node-p "(in-package #:x)")) 39 | (is equal "\"x\"" (test-in-package-node-p "(in-package \"x\")")) 40 | (is equal "x" (test-in-package-node-p "( in-package x )")) 41 | (is equal "x" (test-in-package-node-p "( in-package #| ∿ |# x )")) 42 | (is equal "x" (test-in-package-node-p "(cl:in-package x)")) 43 | (is equal "x" (test-in-package-node-p "(cl::in-package x)")) 44 | (is equal "42" (test-in-package-node-p "(cl::in-package 42)")) 45 | ;; TODO ? Not sure it's worth it lol... 46 | ;; (is equal "x" (test-in-package-node-p "('|CL|::|IN-PACKAGE| x)")) 47 | (is eq nil (test-in-package-node-p "(cl:)")) 48 | (is eq nil (test-in-package-node-p "'(in-package x)"))) 49 | -------------------------------------------------------------------------------- /src/ensure-breeze.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | 3 | This file is used to load breeze's system. 4 | 5 | It is used, for example, by emacs in breeze.el. 6 | 7 | TODO "Checkpoints" 8 | TODO Unload (e.g. delete-package) if it fails to load! 9 | TODO _maybe_ add a variable *breeze-loaded-correctly-p* 10 | 11 | |# 12 | 13 | (cl:in-package #:cl-user) 14 | 15 | (defpackage #:breeze.loader 16 | (:use #:cl)) 17 | 18 | (in-package #:breeze.loader) 19 | 20 | (defun or-die (error callback) 21 | (let (success) 22 | (multiple-value-bind (result condition) 23 | (ignore-errors 24 | (prog1 (funcall callback) 25 | (setq success t))) 26 | (unless (and success (not condition)) 27 | (error (format nil "~A: ~A" error condition))) 28 | result))) 29 | 30 | (or-die "Failed to load asdf." 31 | (lambda () (require 'asdf))) 32 | 33 | (defvar *asd* nil) 34 | 35 | (or-die "Failed to set the path to the system definition." 36 | (lambda () 37 | (unless (and *asd* (probe-file *asd*)) 38 | (setf *asd* 39 | (merge-pathnames "../breeze.asd" *load-truename*))))) 40 | 41 | (or-die "Failed to load the system definition" 42 | (lambda () (asdf:load-asd *asd*))) 43 | 44 | ;; TODO error handling 45 | ;; 46 | ;; TODO if quicklisp is not available, check if all dependencies are 47 | ;; available before trying to load the whole system 48 | ;; 49 | ;; TODO _maybe_ fallback to vendored dependency systems if they can't 50 | ;; be found 51 | ;; 52 | ;; TODO some dependencies and subsystems could be made optional, maybe 53 | ;; this script could take care of setting up some *features*? 54 | 55 | 56 | (if (asdf:component-loaded-p "breeze") 57 | "Already loaded" 58 | (or-die "Failed to load breeze's system." 59 | (lambda () 60 | (prog1 61 | #+quicklisp 62 | (prog1 "Loaded using quicklisp" 63 | (ql:quickload "breeze")) 64 | #-quicklisp 65 | (prog1 66 | "Loaded using asdf:load-system" 67 | (asdf:load-system '#:breeze)) 68 | (format t "~&Breeze loaded!~%"))))) 69 | -------------------------------------------------------------------------------- /docs/glossary.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: bb5c6ad4-0f89-48aa-9295-13e5e248a897 3 | :END: 4 | #+title: Glossary 5 | * Glossary 6 | 7 | #+begin_comment 8 | Trying to document the words/concepts used in the project. 9 | #+end_comment 10 | 11 | ** lisp listener 12 | :PROPERTIES: 13 | :ID: 93da5b9d-9593-45b1-9f71-f49d01c3e95d 14 | :END: 15 | 16 | - More often called "lisp repl". 17 | - I use this term to try to avoid confusion with an hypothetical 18 | future actual REPL. 19 | - You could describe that as a "client-server REPL". 20 | 21 | ** REPL 22 | :PROPERTIES: 23 | :ID: 824a7d5d-d11f-40b0-ae0e-b83ea7dbf812 24 | :END: 25 | 26 | - Stands for "Read-Eval-Print-Loop" 27 | - Most people think about "command line" when they hear REPL, but in 28 | the case of lisp, it usually means a "listener". 29 | 30 | ** e-graph 31 | :PROPERTIES: 32 | :ID: 09937194-8b79-43ff-855a-ec33797b19c7 33 | :END: 34 | 35 | "e-graph" stands for "equivalence graph", it is a data structure. 36 | 37 | See [[id:32155195-1bc4-4f2d-8f6a-12fb0bd68ecc][E-graphs]]. 38 | 39 | 40 | ** Package-local nicknames (PLN) 41 | :PROPERTIES: 42 | :ID: 04bdfbdb-ff84-4d7f-a845-a456c886b8f1 43 | :ROAM_ALIASES: "Local nicknames" 44 | :END: 45 | 46 | Also known as local nicknames, these are nicknames given to packages, 47 | but that are scoped to a specific package. 48 | 49 | ** Buffer 50 | :PROPERTIES: 51 | :ID: aef7ae44-a4e2-4a5e-831b-ffc2ae6f085c 52 | :END: 53 | 54 | In breeze, a buffer is an object that represents a file opened in the 55 | editor. 56 | 57 | ** Point 58 | :PROPERTIES: 59 | :ID: b038c05f-dfba-4154-92ca-a3d467e62d3a 60 | :END: 61 | 62 | The "point" refers to the current position of the cursor in a buffer. 63 | 64 | Note: emacs' point starts at 1, breeze's point starts at 0. 65 | 66 | ** Binding 67 | :PROPERTIES: 68 | :ID: 5a2bc469-d927-401e-aa77-81b00a9dcd42 69 | :END: 70 | 71 | An association from a name to a value. 72 | 73 | ** Substitution 74 | :PROPERTIES: 75 | :ID: ea4e8881-2ca6-4c8d-b2dc-dcbd5fdad721 76 | :END: 77 | 78 | A substitution is a set of [[*Binding][bindings]]. 79 | 80 | See [[id:fba498f1-2e4c-4e4d-aa96-0f7a9b4ff369][Substitutions]] for more details 81 | -------------------------------------------------------------------------------- /docs/support_for_tests.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: a9a98f8e-b097-4e8c-a2d1-92d8b8a26707 3 | :END: 4 | #+title: Support for tests 5 | 6 | * PROtocol and TESTcase manager :test:3rd_parties: 7 | 8 | [[https://github.com/phoe/protest][phoe/protest]] 9 | 10 | PROTEST is a tool for defining protocols and test cases written in and 11 | for Common Lisp. 12 | * trying to find discrepancies between the packages and test packages :test: 13 | 14 | or betweew test system and the system under test 15 | 16 | I consider this task "DONE" because I did _try_ to find discrepancies 17 | between the package ~breeze.refactor~ and ~breeze.test.refactor~. I 18 | used the convention that each "command" defined in ~breeze.refator~ 19 | should have a test with the same name (i.e. the same symbol-name). I 20 | have a test that fails if this "invariant" is not held. 21 | 22 | In the future, I would like to 23 | 24 | ** TODO Figure out how to generalize "finding missing tests by discrepancies" :test:ux:config: 25 | 26 | Not everyone is going to have the same conventions. 27 | 28 | ** TODO Improve the current test by looking for prefix instead 29 | 30 | E.g package ~a~ has an exported symbol ~s~, it's corresponding test 31 | package is ~a.test~. 32 | 33 | The current implementation would try to find a test named ~s~ in 34 | ~a.test~ (for example ~a.test::s~, or ~"s"~ (test names can be string 35 | in parachute), it would be nice to have it also considers tests that 36 | have the _prefix_ ~s~. 37 | 38 | Why? Because I have some automatically generated test (a bit like 39 | snapshot tests), it's very convenient that they have the same name as 40 | the thing they are testing. Using a prefix would let me have multiple 41 | kind of tests for each (automatically generated or not). 42 | 43 | Do I want to check if each "types" of tests are implemented? 44 | 45 | Can parachute's `deftest`'s be easily augmented with some metadata? 46 | That might help too. 47 | 48 | * TODO Integrate with multiple test framework :test: 49 | 50 | See @phoe's [[https://github.com/phoe/protest][phoe/protest]]. 51 | 52 | * TODO It's too easy to kill the test-runner :tech_debt:ux: 53 | -------------------------------------------------------------------------------- /scratch-files/definition.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.definition 3 | (:documentation "Provides replacements for \"definition forms\" (such as defun and defmacro). 4 | The goal is to (portably) make sure we keep the definitions and not just their [compiled] results.") 5 | (:use :cl) 6 | (:shadow cl:defun cl:fmakunbound) 7 | (:export 8 | #:*function* 9 | #:*function-redifinition-hooks* 10 | #:defun 11 | #:fmakunbound 12 | #:function-body)) 13 | 14 | (in-package #:breeze.definition) 15 | 16 | (defvar *function* (make-hash-table) 17 | "Set of all functions defined with breeze.definition:defun") 18 | 19 | (defvar *function-redifinition-hooks* () 20 | "List of functions to call when a function is redefined") 21 | 22 | (cl:defun flag-funtion-redifinition (name) 23 | "Calls each function in *funtion-redifinition-hooks*." 24 | (loop :for hook :in *function-redifinition-hooks* 25 | :do (funcall hook name))) 26 | 27 | (defmacro defun (&whole whole name lambda-list &body body) 28 | "Define a functions and saves its definition in memory, flag a function redefinition." 29 | `(progn (cl:defun ,name ,lambda-list 30 | ,@body) 31 | (setf (gethash ',name *function*) ',whole) 32 | (flag-funtion-redifinition ',name) 33 | ',name)) 34 | 35 | (cl:defun fmakunbound (name) 36 | "Make NAME have no global function definition." 37 | (cl:fmakunbound name) 38 | (remhash name *function*)) 39 | 40 | (cl:defun function-body (name) 41 | "Get the body of a function by name" 42 | (cdddr (gethash name *function*))) 43 | 44 | ;; TODO defmacro 45 | ;; TODO defgeneric 46 | ;; TODO defmethod 47 | ;; TODO defclass 48 | ;; TODO what about closures? 49 | 50 | #| 51 | (loop :for symbol :being :the :external-symbol :of :cl 52 | :when (alexandria:starts-with-subseq "DEF" (symbol-name symbol)) 53 | :collect symbol) 54 | 55 | (DEFINE-SYMBOL-MACRO 56 | DEFCLASS 57 | DEFUN 58 | DEFSETF 59 | DEFMETHOD 60 | DEFCONSTANT 61 | DEFINE-METHOD-COMBINATION 62 | DEFSTRUCT 63 | DEFVAR 64 | DEFGENERIC 65 | DEFTYPE 66 | DEFMACRO 67 | DEFPARAMETER 68 | DEFPACKAGE 69 | DEFINE-COMPILER-MACRO 70 | DEFINE-MODIFY-MACRO 71 | DEFINE-CONDITION 72 | DEFINE-SETF-EXPANDER) 73 | |# 74 | -------------------------------------------------------------------------------- /src/cmds/capture.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.capture 2 | (:documentation "Utilities for quick capture and management of code.") 3 | (:use #:cl) 4 | (:import-from #:breeze.string 5 | #:remove-indentation 6 | #:whitespacep) 7 | (:import-from #:breeze.command 8 | #:choose 9 | #:define-command 10 | #:message 11 | #:find-file) 12 | (:import-from #:breeze.config 13 | #:*capture-folder* 14 | #:*capture-template*) 15 | (:export #:capture)) 16 | 17 | (in-package #:breeze.capture) 18 | 19 | (defun list-existing-captured-files () 20 | (mapcar #'pathname-name 21 | (directory 22 | (merge-pathnames "*.lisp" *capture-folder*)))) 23 | 24 | (defun populate (pathname) 25 | (with-open-file (output pathname 26 | :if-exists :error 27 | :if-does-not-exist :create 28 | :direction :output) 29 | (format output (remove-indentation *capture-template*)))) 30 | 31 | (define-command capture () 32 | "Quickly create a lisp file in a pre-determined directory." 33 | ;; TODO Make sure *capture-folder* is set 34 | ;; TODO Otherwise, ask the user to choose a directory _and save it_ 35 | (unless *capture-folder* 36 | (error "*capture-folder* not set")) 37 | ;; TODO ensure that *capture-folder* is a valid directory pathname 38 | ;; TODO check if *capture-folder* exists 39 | (unless (probe-file *capture-folder*) 40 | (if (breeze.command:ask-y-or-n-p "The capture folder ~s does not exist, create it? " *capture-folder*) 41 | (ensure-directories-exist *capture-folder*) 42 | (error "The capture folder ~s does not exist" *capture-folder*))) 43 | ;; We use "chose" instead of "read-string" so that the user can 44 | ;; easily see if he's trying to create a file with a name that 45 | ;; already exists. 46 | (let* ((files (list-existing-captured-files)) 47 | (name (concatenate 'string 48 | (choose "Name of the file and package: " files) 49 | ".lisp")) 50 | (pathname (merge-pathnames name *capture-folder*))) 51 | (unless (probe-file pathname) 52 | (populate pathname)) 53 | (find-file pathname))) 54 | -------------------------------------------------------------------------------- /scripts/org-publish-project.el: -------------------------------------------------------------------------------- 1 | 2 | (message "Installing packages (from ELPA)") 3 | 4 | (when package-enable-at-startup 5 | (package-initialize) 6 | (setf package-selected-packages 7 | '(htmlize)) 8 | (package-install-selected-packages t)) 9 | 10 | 11 | (message "Publishing...") 12 | 13 | (require 'org) 14 | (require 'org-id) 15 | (require 'htmlize) 16 | 17 | ;; See (describe-variable 'org-publish-project-alist) 18 | ;; See https://orgmode.org/manual/Publishing-options.html for more options 19 | (let* ((forcep t) ; "forcep" is for interactive sessions. 20 | (org-id-link-to-org-use-id t) 21 | (default-directory 22 | (expand-file-name 23 | (concat 24 | (file-name-directory (or load-file-name buffer-file-name)) 25 | ".."))) 26 | (root "docs") 27 | (project-alist 28 | `("breeze" 29 | :base-directory ,root 30 | :publishing-function org-html-publish-to-html 31 | :publishing-directory "./public" 32 | 33 | :author "Francis St-Amour" 34 | :creator "Francis St-Amour" 35 | :with-author nil 36 | 37 | :html-style nil 38 | 39 | :html-validation-link nil 40 | 41 | :html-link-up "" ; this is the default 42 | :html-link-home "index.html" 43 | 44 | ;; this is a format string, the first %s is the up "url", 45 | ;; and the second is the "home" url. Here, we assume the 46 | ;; first is empty 47 | :html-home/up-format "
%s 48 | HOME 49 |
" 50 | :html-head "" 51 | 52 | :auto-sitemap t 53 | ;; https://emacs.stackexchange.com/questions/70824/how-to-use-makeindex 54 | :makeindex t ;; TODO need to add a bunch of #+index: term 55 | :with-toc nil 56 | ))) 57 | (org-id-update-id-locations (directory-files root t "\\.org$")) 58 | (org-publish project-alist forcep) 59 | (dolist (file (directory-files "docs/" t "listing-.*\\.html$")) 60 | (copy-file file "public/" :ok-if-already-exists)) 61 | (copy-file "docs/reference.html" "public/" :ok-if-already-exists) 62 | (copy-file "docs/style.css" "public/" :ok-if-already-exists)) 63 | -------------------------------------------------------------------------------- /src/channel.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:breeze.channel 3 | (:documentation "A simple channel implementation.") 4 | (:use #:cl) 5 | (:export #:make-channel 6 | #:channelp 7 | #:channel 8 | #:send 9 | #:receive 10 | #:emptyp)) 11 | 12 | (in-package #:breeze.channel) 13 | 14 | (defclass channel () 15 | ((queue 16 | :initform (cons nil nil) 17 | :documentation "The content of the channel's queue.") 18 | (lock 19 | :initform (bt2:make-lock) 20 | :documentation "Mutex lock") 21 | (datap 22 | :initform (bt2:make-semaphore) 23 | :documentation "Used to signal available data for the readers.")) 24 | (:documentation "A minimal unbounded, queue-backed (FIFO) channel.")) 25 | 26 | (defun make-channel () 27 | "Make a new unbounded channel." 28 | (make-instance 'channel)) 29 | 30 | (defun channelp (x) 31 | (typep x 'channel)) 32 | 33 | (defmethod print-object ((channel channel) stream) 34 | (print-unreadable-object (channel stream :type t :identity t) 35 | (format stream "~a" (length (car (slot-value channel 'queue)))))) 36 | 37 | (declaim (inline empty-queue-p)) 38 | (defun empty-queue-p (q) 39 | (and (null (car q)) (null (cdr q)))) 40 | 41 | (declaim (inline enqueue)) 42 | (defun enqueue (q v) 43 | (let ((end (list v))) 44 | (if (empty-queue-p q) 45 | (setf (car q) end) 46 | (setf (cddr q) end)) 47 | (setf (cdr q) end))) 48 | 49 | (declaim (inline dequeue)) 50 | (defun dequeue (q) 51 | (prog1 (pop (car q)) 52 | (unless (car q) 53 | (setf (cdr q) nil)))) 54 | 55 | 56 | 57 | (defun send (c v) 58 | "Send the value V into the channel C, doesn't block (much)." 59 | (with-slots (queue lock datap) c 60 | (bt2:with-lock-held (lock #| TODO :timeout timeout |#) 61 | (enqueue queue v)) 62 | (bt2:signal-semaphore datap))) 63 | 64 | (defun receive (c) 65 | "Receive a value from the channel C, blocks if there's not message 66 | available." 67 | (with-slots (queue lock datap) c 68 | (loop 69 | (bt2:wait-on-semaphore datap #| TODO :timeout timeout |#) 70 | (bt2:with-lock-held (lock) 71 | (unless (empty-queue-p queue) 72 | (return (dequeue queue))))))) 73 | 74 | (defun emptyp (c) 75 | (with-slots (queue lock datap) c 76 | (bt2:with-lock-held (lock) 77 | (empty-queue-p queue)))) 78 | -------------------------------------------------------------------------------- /src/pattern/rewrite.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:breeze.pattern) 2 | 3 | 4 | ;;; Match substitution 5 | 6 | (defun pattern-substitute (pattern bindings &optional (result-type 'vector)) 7 | (cond 8 | ((and pattern (null bindings)) pattern) 9 | ((null pattern) nil) 10 | ((and pattern bindings) 11 | (check-type pattern atom) 12 | (check-type bindings (or binding substitutions (eql t))) 13 | (flet ((substitute1 (x) 14 | (etypecase x 15 | ((or simple-var var) 16 | (alexandria:if-let ((binding (find-binding bindings (name x)))) 17 | (to binding) 18 | ;; TODO this could signal a condition (binding not 19 | ;; found) 20 | x)) 21 | ((or symbol number) x)))) 22 | (if (vectorp pattern) 23 | (map result-type 24 | ;; Note: we could've recurse directly into 25 | ;; pattern-subtitute, but not doing so make tracing 26 | ;; (and debugging) tremenduously easier. 27 | #'(lambda (subpattern) 28 | (if (vectorp subpattern) 29 | (pattern-substitute subpattern bindings result-type) 30 | (substitute1 subpattern))) 31 | pattern) 32 | (substitute1 pattern)))))) 33 | 34 | 35 | 36 | ;;; Rules and rewrites 37 | 38 | 39 | ;; TODO "rules" would be "bidirectional" and "rewrites" wouldn't. 40 | ;; TODO (defun rule (a b) ...) 41 | ;; TODO (defun make-rewrite (antecedent consequent) ...) 42 | 43 | #++ (progn 44 | (defclass abstract-rule () ()) 45 | 46 | (defclass rule (abstract-rule) ()) 47 | 48 | (defun make-rule (a b) 49 | (list :rule 50 | (compile-pattern a) 51 | (compile-pattern b))) 52 | 53 | (defun make-rewrite (a b) 54 | (list :rewrite 55 | (compile-pattern a) 56 | (compile-pattern b)))) 57 | 58 | (defun make-rewrite (pattern template) 59 | (cons ;; TODO use a class instead 60 | (compile-pattern pattern) 61 | (compile-pattern template))) 62 | 63 | (defun rewrite-pattern (rewrite) 64 | "Get the pattern of a REWRITE rule." 65 | (car rewrite)) 66 | 67 | (defun rewrite-template (rewrite) 68 | "Get the template of a REWRITE rule." 69 | (cdr rewrite)) 70 | -------------------------------------------------------------------------------- /docs/examples_of_code_that_is_not_handled_correctly_by_slime_and_sly.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 6055422c-7954-4c12-8f0b-32cd757fb2ac 3 | :END: 4 | #+title: Examples of code that is not handled correctly by slime and sly 5 | 6 | * Defuns with bad indentation 7 | :PROPERTIES: 8 | :ID: 82bb6f4b-bd23-46b9-8243-d3dbf5c9ad81 9 | :END: 10 | 11 | With slime, there is no place in the next example where the command 12 | ~slime-compile-defun~ is able to compile the function ~g~, it will 13 | always either compile ~f~ or ~h~. 14 | 15 | #+begin_src lisp 16 | (defun f ()) 17 | 18 | (defun g ()) 19 | 20 | (defun h()) 21 | #+end_src 22 | 23 | * Symbols with escapes 24 | :PROPERTIES: 25 | :ID: ef867d3a-897f-43e6-b406-244f448e479a 26 | :END: 27 | 28 | This is equivalent to ~'cl:in-package~. 29 | 30 | #+begin_src lisp 31 | '|CL|::|IN-PACKAGE| 32 | #+end_src 33 | 34 | - ~slime-eval-defun~ either evaluates this to ~`cl~ or (correctly) to 35 | ~'in-package~ depending on whether the point is on the =|CL|= or 36 | =|IN-PACKAGE|=. 37 | - ~slime-eval-last-expression~ signals a condition of type ~unbound-variable~ 38 | 39 | #+begin_example 40 | The variable IN-PACKAGE is unbound. 41 | [Condition of type UNBOUND-VARIABLE] 42 | #+end_example 43 | 44 | * False positive matching =in-package= 45 | :PROPERTIES: 46 | :ID: 5ceaab80-381e-4e68-b0db-d97020412d8f 47 | :END: 48 | 49 | By default the current package is found using 50 | ~slime-search-buffer-package~, which uses a regex to find an 51 | "in-package" form in the current buffer. 52 | 53 | This first exampe is a bit contrived, but it's enough to show the 54 | issue: there is a string that contains an "in-package" form and the 55 | regex picks it up regardless of it being inside a string. 56 | 57 | #+begin_src lisp 58 | (defpackage #:foo 59 | (:use #:cl)) 60 | 61 | (in-package #:foo) 62 | 63 | (defun in-package-bar-p (x) 64 | (equal " 65 | (in-package bar)" x)) 66 | 67 | ,*package* 68 | ;; => # 69 | #+end_src 70 | 71 | The second example shows something more problemaic: the form 72 | ~(in-package-bar-p x)~ is being picked up by slime's regexp. (I think 73 | it should be possible to fix the regex though!) 74 | 75 | #+begin_src lisp 76 | (in-package #:foo) 77 | 78 | ,*package* 79 | ;; => # 80 | 81 | (defun foo (x) 82 | (in-package-bar-p x)) 83 | 84 | ,*package* 85 | ;; => # 86 | #+end_src 87 | -------------------------------------------------------------------------------- /src/thread.lisp: -------------------------------------------------------------------------------- 1 | (uiop:define-package #:breeze.thread 2 | (:documentation "Utilities to help with concurrent programming.") 3 | (:use #:cl) 4 | (:import-from #:breeze.xref 5 | #:function-designator-p) 6 | (:export 7 | #:find-threads 8 | #:find-threads-by-name 9 | #:find-worker-threads 10 | #:kill-threads 11 | #:kill-threads-by-name 12 | #:kill-worker-threads 13 | #:breeze-kill-worker-threads)) 14 | 15 | (in-package #:breeze.thread) 16 | 17 | 18 | ;;; Thread management 19 | 20 | (defun find-threads (&optional predicate (exclude-self-p t)) 21 | (let ((current-thread (when exclude-self-p (bt:current-thread)))) 22 | (remove-if-not #'(lambda (thread) 23 | (and (not (eq current-thread thread)) 24 | (if predicate 25 | (funcall predicate thread) 26 | t))) 27 | (bt:all-threads)))) 28 | 29 | (defun find-threads-by-prefix (prefix &key (exclude-self-p t)) 30 | (find-threads #'(lambda (thread) 31 | (alexandria:starts-with-subseq prefix (bt:thread-name thread))) 32 | exclude-self-p)) 33 | 34 | (defun find-threads-by-name (name &key (exclude-self-p t)) 35 | (find-threads #'(lambda (thread) 36 | (string= (bt:thread-name thread) name)) 37 | exclude-self-p)) 38 | 39 | (defun find-worker-threads (&optional (exclude-self-p t)) 40 | (find-threads-by-name "worker" :exclude-self-p exclude-self-p)) 41 | 42 | (defun %kill-threads (threads) 43 | (prog1 44 | ;; Always return the number of threads 45 | (length threads) 46 | (when threads 47 | (mapcar #'bordeaux-threads:destroy-thread threads) 48 | (format *debug-io* "~&Killed ~d threads.~%" (length threads))))) 49 | 50 | (defun kill-threads (predicate) 51 | "Find threads by predicate, and destroy them." 52 | (%kill-threads (find-threads predicate))) 53 | 54 | (defun kill-threads-by-name (name) 55 | "Find threads by name, then destroy them." 56 | (%kill-threads (find-threads-by-name name))) 57 | 58 | ;; TODO make a command with this... 59 | (defun kill-worker-threads () 60 | "Find threads named \"worker\", then destroy them." 61 | (%kill-threads (find-threads-by-name "worker"))) 62 | 63 | (breeze.command:define-command breeze-kill-worker-threads () 64 | "Find threads named \"worker\", then destroy them." 65 | (kill-worker-threads)) 66 | -------------------------------------------------------------------------------- /src/xref.lisp: -------------------------------------------------------------------------------- 1 | 2 | (uiop:define-package #:breeze.xref 3 | (:documentation "Cross-reference and introspection") 4 | (:mix :cl #:breeze.utils #:alexandria) 5 | (:export 6 | #:calls-who 7 | ;; Utilities 8 | #:find-packages-by-prefix 9 | ;; Symbol inspection 10 | #:generic-method-p 11 | #:specialp 12 | #:macrop 13 | #:simple-function-p 14 | #:classp 15 | #:externalp 16 | #:function-designator-p)) 17 | 18 | (in-package #:breeze.xref) 19 | 20 | (defun find-packages-by-prefix (prefix) 21 | "Find all packages whose name starts with the given prefix (case insensitive by default)." 22 | (loop 23 | :with prefix = (string-downcase prefix) 24 | :for package :in (list-all-packages) 25 | :when (starts-with-subseq prefix 26 | (string-downcase 27 | (package-name package))) 28 | :collect package)) 29 | 30 | (defun generic-method-p (symbol) 31 | "Returns T if SYMBOL designates a generic method" 32 | (and (fboundp symbol) 33 | (subtypep 34 | (type-of (fdefinition symbol)) 35 | 'standard-generic-function))) 36 | 37 | (defun specialp (symbol) 38 | "Return true if SYMBOL is a special variable." 39 | (and (symbolp symbol) 40 | (or (boundp symbol) 41 | (eval `(let (,symbol) 42 | (declare (ignorable ,symbol)) 43 | (boundp ',symbol)))))) 44 | 45 | (defun macrop (symbol) 46 | "Return true if SYMBOL designates a macro." 47 | (and (symbolp symbol) 48 | (macro-function symbol))) 49 | 50 | (defun simple-function-p (symbol) 51 | "Return true if SYMBOL is a function that is nor a macro nor a generic function." 52 | (and (fboundp symbol) 53 | (not (generic-method-p symbol)) 54 | (not (macrop symbol)))) 55 | 56 | (defun classp (symbol) 57 | "Return true if SYMBOL designate a class." 58 | (find-class symbol nil)) 59 | 60 | (defun externalp (symbol) 61 | (and (symbol-package symbol) 62 | (eq :external 63 | (nth-value 1 (find-symbol (symbol-name symbol) 64 | (symbol-package symbol)))))) 65 | 66 | (defun function-designator-p (designator) 67 | (or (functionp designator) 68 | (and (symbolp designator) 69 | (fboundp designator) 70 | (not (macrop designator))))) 71 | 72 | ;; TODO 73 | ;; (function-designator-p #'first) 74 | ;; (function-designator-p 'first) 75 | ;; (function-designator-p '(a b c)) 76 | ;; (function-designator-p 'defmacro) 77 | ;; (function-designator-p nil) 78 | ;; TODO (setf x) 79 | -------------------------------------------------------------------------------- /src/cmds/editing.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.editing 2 | (:documentation "Text and structural editing") 3 | (:use #:cl 4 | #:breeze.parser 5 | #:breeze.command 6 | #:breeze.buffer) 7 | (:export #:kill-sexp)) 8 | 9 | (in-package #:breeze.editing) 10 | 11 | ;; (trace replace-region) 12 | 13 | #| 14 | ;; TODO add NOTE: "can't splice comment", but I wish I could 15 | ;; e.g. ` ;; (some | code)` 16 | ;; paredit-splice-sexp or paredit-splice-sexp-killing-backward 17 | 18 | 19 | (|asdf)qwer 20 | M- paredit-splice-sexp-killing-backward 21 | asfdqwer 22 | -- should be 23 | asdf qwer 24 | 25 | M-( paredit-wrap-round 26 | 27 | I just had to wrap a bunch of forms with (multiple-value-list ...) It 28 | would have been nice to have something to help with this... perhaps 29 | using the region/rectangle? 30 | 31 | TODO next-alternative/previous alternative (e.g. :accessor <-> :reader <-> :writer, in the right context) 32 | 33 | |# 34 | 35 | (define-command kill-sexp () 36 | "Kill the expression following point." 37 | (let* ((buffer (current-buffer)) 38 | (point (point buffer)) 39 | (node-iterator (copy-iterator buffer))) 40 | #++ (break "point: ~D, start: ~D end: ~D ~s" point 41 | (start node-iterator) 42 | (end node-iterator) (node-string node-iterator)) 43 | (flet ((maybe-include-next-node (it) 44 | (if (= point (1- (end it))) 45 | (1- (end it)) 46 | (end 47 | (let ((next-node (next-sibling it))) 48 | ;; (break "next-node: ~s" next-node) 49 | (if (and next-node (whitespace-node-p next-node)) 50 | next-node 51 | it)))))) 52 | (cond 53 | ((whitespace-node-p (value node-iterator)) 54 | (let* ((lastp (lastp node-iterator)) 55 | (start (if lastp (start node-iterator) point))) 56 | (unless lastp 57 | (next node-iterator)) 58 | (replace-region start (maybe-include-next-node node-iterator) ""))) 59 | ((= point (end node-iterator)) 60 | (message "END")) 61 | (t 62 | (replace-region point (maybe-include-next-node node-iterator) "")))))) 63 | 64 | #| 65 | ;; TODO 66 | (define-command forward-slurp) 67 | 68 | ;; TODO 69 | (define-command backward-slurp) 70 | 71 | ;; TODO 72 | (define-command forward-barf) 73 | 74 | ;; TODO 75 | (define-command backward-barf) 76 | |# 77 | 78 | 79 | #++ ;; TODO 80 | (define-command beginning-of-defun () 81 | "Move backward to the beginning of a defun.") 82 | -------------------------------------------------------------------------------- /tests/iterator.lisp: -------------------------------------------------------------------------------- 1 | ;; TODO move under tests/pattern 2 | 3 | (defpackage #:breeze.test.iterator 4 | (:documentation "Tests for the package breeze.iterator") 5 | (:use #:cl #:breeze.iterator) 6 | (:import-from #:parachute 7 | #:define-test 8 | #:define-test+run 9 | #:is 10 | #:isnt 11 | #:true 12 | #:false 13 | #:of-type 14 | #:fail)) 15 | 16 | (in-package #:breeze.test.iterator) 17 | 18 | (define-test+run vector-iterator 19 | (let* ((vector #(1 2 3)) 20 | (iterator (make-vector-iterator vector))) 21 | (is eq vector (slot-value iterator 'vector)) 22 | (is = 0 (current-position iterator)) 23 | (progn 24 | (true (firstp iterator)) 25 | (false (before-last-p iterator)) 26 | (false (lastp iterator))) 27 | (progn 28 | (next iterator) 29 | (false (firstp iterator)) 30 | (true (before-last-p iterator)) 31 | (false (lastp iterator))) 32 | (progn 33 | (next iterator) 34 | (false (firstp iterator)) 35 | (false (before-last-p iterator)) 36 | (true (lastp iterator)))) 37 | (let* ((vector #(1 2 3)) 38 | (iterator (make-vector-iterator vector :position 2))) 39 | (is eq vector (slot-value iterator 'vector)) 40 | (is = 2 (current-position iterator))) 41 | (is equal (list 2 3) 42 | (collect (make-vector-iterator #(1 2 3) :position 1))) 43 | (false 44 | (collect (make-vector-iterator #(1 2 3) :position 10)))) 45 | 46 | (define-test+run tree-iterator 47 | (let* ((root #(1 2 3)) 48 | (iterator (make-tree-iterator root))) 49 | (is eq root (subtree iterator)) 50 | (is = 0 (pos iterator)) 51 | (is equal '(1 2 3) (collect iterator))) 52 | (let* ((iterator (make-tree-iterator #()))) 53 | (true (donep iterator))) 54 | ;; Testing push-subtree and pop-subtree 55 | (is equal '(1 2 3 4) 56 | (let ((iterator (make-tree-iterator #(1 #(2 3) 4)))) 57 | (list (prog1 (value iterator) (next iterator)) 58 | (progn (push-subtree iterator (value iterator)) 59 | (value iterator)) 60 | (progn (next iterator) 61 | (value iterator)) 62 | (progn (pop-subtree iterator) (next iterator) 63 | (value iterator)))))) 64 | 65 | #++ 66 | (collect 67 | (make-concat-iterator 68 | (vector 69 | (make-vector-iterator #(1 2 3)) 70 | (make-vector-iterator #(a b c)))) 71 | :limit 10) 72 | 73 | #++ 74 | (let ((it (make-tree-iterator 75 | #(a b #(c) #(d #(e)))))) 76 | (collect it :limit 2)) 77 | -------------------------------------------------------------------------------- /docs/faq_from_newbies_about_common_lisp.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 31236780-159e-4a58-9019-37f57f5b4997 3 | :END: 4 | #+title: FAQ from newbies about common lisp 5 | 6 | This is useful for breeze's development, to figure out the pain 7 | points. Ideally, this should go somewhere else, like in tutorial, a 8 | cookbook or some kind of reference (like [[https://github.com/fstamour/lisp-docs.github.io][lisp-docs.github.io]]). 9 | 10 | See also GitHub issue: https://github.com/fstamour/breeze/issues/35 11 | 12 | * FAQ from newbies about common lisp 13 | 14 | ** What's the difference between load and require? 15 | 16 | ** What's asdf v. quicklisp v. packages v. "os packages"? 17 | 18 | ** The heck is RPLACA? 19 | 20 | ** What's the difference between =setf= and =setq=? 21 | 22 | https://stackoverflow.com/questions/869529/difference-between-set-setq-and-setf-in-common-lisp 23 | 24 | ** Why use #:symbol (especially in =defpackage=)? 25 | 26 | ** Why start a file with =(cl:in-package #:cl-user)=? 27 | 28 | ** Why interactivity is important? 29 | 30 | They don't actually ask that, they usually just don't think or know 31 | about it. 32 | 33 | Here's something that does an OK job at explaining the importance: 34 | https://technotales.wordpress.com/2007/10/03/like-slime-for-vim/ 35 | 36 | ** What's the difference between ~defvar~ and ~defparameter~? 37 | 38 | ** Something about using ~setf~ to create variables... 39 | 40 | ** A symbol can represent many things 41 | 42 | - variables/symbol macros 43 | - functions/macros 44 | - classes/conditions/types 45 | - method combinations 46 | - block names 47 | - catch tags 48 | - tagbody tags 49 | - restarts 50 | - packages 51 | - compiler macros 52 | - slot names 53 | - compiler macros 54 | 55 | ** When coming from another language 56 | 57 | *** How to create a function-local variable? 58 | 59 | ** Proclaim v.s. Declaim v.s. Declare 60 | 61 | http://www.lispworks.com/documentation/lw50/LWUG/html/lwuser-90.htm 62 | 63 | ** How packages and symbols works? 64 | 65 | https://flownet.com/ron/packages.pdf 66 | 67 | ** Alternatives to the Hyperspec 68 | 69 | - [[http://clqr.boundp.org/download.html][Common Lisp Quick Reference]] 70 | - Ultraspec (dead) 71 | - Simplified something something 72 | - The lisp cookbook 73 | 74 | ** What the hell are pathnames? 75 | 76 | - Don't forget trailing backslashes for directories. 77 | 78 | ** Where are the functions to operate on strings? 79 | 80 | - Use the functions that operate on sequences. 81 | - Use libraries, like alexandria, split-sequences, serapeum, etc. 82 | 83 | * Related notes 84 | 85 | - [[id:b139c21c-3a35-4b69-acd5-00b9d71090ce][Helping with setting up slime for remote sessions]] 86 | -------------------------------------------------------------------------------- /scratch-files/refactor-scratch.lisp: -------------------------------------------------------------------------------- 1 | ;;; Trying to refactor stuff 2 | 3 | (cl:defpackage #:refactor-scratch 4 | (:use :cl #:breeze.parser #:breeze.workspace #:breeze.analysis)) 5 | 6 | (cl:in-package #:refactor-scratch) 7 | 8 | ;; Trying to update a (parachute) assertion 9 | (let* ((*workspace* (make-workspace)) 10 | (buffer (make-buffer :string "(is = (* 2 3) x)" 11 | :point 1)) 12 | ($node (node-iterator buffer))) 13 | (progn ;; list 14 | ;; (node-string $node) 15 | ;; T 16 | ;; (match (compile-pattern `(:symbol "IS")) $node) 17 | #++ (node-string 18 | (to 19 | (find-binding (match (compile-pattern `((:symbol "IS") ?x)) $node) '?x))) 20 | (matching ($node ((:symbol "IS" #++ "PARACHUTE") 21 | ?cmp ?expected (:var ?got (:maybe :_)))) 22 | #++ (loop :for (from . to) :in (breeze.test.pattern::bindings-alist bindings) 23 | :collect (cons from (node-string to))) 24 | (list ?cmp ?expected ?got) 25 | (mapcar 'node-string (list ?cmp ?expected ?got)) 26 | bindings))) 27 | 28 | (let* ((*workspace* breeze.test.workspace:*breeze-workspace*) 29 | (buffer (find-buffer "tests/documentation.lisp")) 30 | (needle )) 31 | (search "(is string=" (source buffer))) 32 | 33 | (defun test-refactor-if (string) 34 | (let ((node (first (parse-string string)))) 35 | (when (and (if-p node) 36 | (= 3 (node-length node))) 37 | (setf (node-content (first (node-content node))) 38 | (if (null-node-p (node-lastcar node)) 39 | "unless" 40 | "when")) 41 | (unparse-to-string (list node))))) 42 | 43 | (test-refactor-if "(if x nil)") 44 | "(unless x nil)" 45 | 46 | (test-refactor-if "(if x 32)") 47 | "(when x 32)" 48 | 49 | (test-refactor-if 50 | "(if #| comment |# 'x NIL)") 51 | "(unless #| comment |# 'x nil)" 52 | 53 | (test-refactor-if 54 | "(IF 55 | 'x NIL)") 56 | "(unless 57 | 'x nil)" ;; FIXME it should be "NIL" 58 | 59 | 60 | 61 | ;; Forms to add to defsystem to test with parachute 62 | (let ((system-name "breeze")) 63 | (let ((test-system (format nil "~a/test" system-name)) 64 | (test-package (format nil "~a/test" system-name)) 65 | (test-function system-name)) 66 | (format nil 67 | "~{~A~}" 68 | (list 69 | ":in-order-to ((test-op (load-op #:" test-system"))) 70 | :perform 71 | (test-op (o c) 72 | (symbol-call 73 | '#:parachute '#:test 74 | (find-symbol (symbol-name '#:" test-function ") 75 | (find-package '#:" test-package ")) 76 | :report (find-symbol \"INTERACTIVE\" \"PARACHUTE\")))")))) 77 | -------------------------------------------------------------------------------- /docs/introduction.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: d08ab932-1204-4e7c-9869-40fc53500071 3 | :END: 4 | #+title: Introduction 5 | 6 | * What is this project? 7 | :PROPERTIES: 8 | :ID: 9b35d7aa-cb8a-433f-b0cb-ba1305740b21 9 | :END: 10 | 11 | This is a git repository that contains lots of common lisp code that I 12 | use to make developing with common lisp easier. It is a personal 13 | project that I work on from time to time, but that I use (and break) 14 | pretty much all the time. 15 | 16 | * Features 17 | 18 | - Emacs integration 19 | - Integration with quickproject 20 | - Context-aware, configurable snippets 21 | - Command for quick code capture (trying out code in a new file) 22 | - Implemented in common lisp to be able to port it to other editors in 23 | the future 24 | 25 | Currently, breeze's main interface is emacs; =breeze.el= adds a few 26 | commands and one minor-mode. 27 | 28 | #+begin_comment 29 | The one binding calls a command named ~breeze-quickfix~ (might rename 30 | in the future). This command suggests applicable actions given the 31 | current context (file name, file content, position in the file, 32 | etc.). For example, if the file ends with ".asd" it will suggest a 33 | command to insert a ~defsystem~ form. If breeze was already 34 | configured, it will pre-fill the ~:maintainer~, ~:author~ and 35 | ~:licence~ fields. Another example is that if the file is empty, or 36 | contains only comments, it will suggest to insert a ~defpackage~ or 37 | ~uiop:define-package~ form. It is also able to detect when you're 38 | trying to edit/evaluate forms that are in a package that doesn't 39 | exists (=did you forget to evaluate the ~defpackage~ form?=). 40 | 41 | The integration with quickproject is pretty simple and lets you 42 | quickly create new projects from the comfort of your editor. The 43 | integration consists of one command that asks you for some 44 | information, like the project name and licence. It takes some default 45 | values from breeze's configuration, but lets you change them. All 46 | this to ease the use of quickproject. 47 | 48 | Another simple command that helps me is ~breeze-capture~, it creates a 49 | new file in a pre-determined (must be configured) folder and fills it 50 | with some pre-configured content (template) and lets you code right 51 | away. This could've easily be done in emacs (that's how I prototyped 52 | the first version), but doing this in common lisp makes it easy to 53 | port it to other editors (or just the repl) in the future. 54 | 55 | I must stress that this whole project is in constant flux, and until I 56 | add more and more tests, stuff might break any time. 57 | #+end_comment 58 | 59 | * Next 60 | 61 | [[id:306350c9-0fb5-478b-958b-b35cae726280][Getting Started]] 62 | -------------------------------------------------------------------------------- /src/cmds/completion.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | 3 | contextual completions: 4 | 5 | (with-slot (B) A) 6 | - A should probably comes from nearby 7 | - A should be a "clos object", or a form that evaluates to one 8 | - B should be a one of A's slots 9 | 10 | (in-package #:A) 11 | - A must be the name or nickname of a package 12 | - silly question: can A be a local package nickname??? 13 | 14 | (defpackage name 15 | (A ) 16 | (:use B) 17 | (:import-from C D) 18 | (:export E) 19 | (:nicknames F) 20 | (:shadow G) 21 | (:shadowing-import H I) 22 | (:intern J) 23 | (:documentation K) 24 | (:size L)) 25 | - A must be one of defpackage's options 26 | - B must not be already in another :use option 27 | - that would be redundant 28 | - C should probably not be already in another :import-from options 29 | - though it's totally legal, and could be done on purpose 30 | - B, C, H must be package designators 31 | - F must be a "new" package designator 32 | - E, J and G must be symbol designators 33 | - K must be a string 34 | - L must be a positive integer 35 | - D must be a symbol designator for a symbol from the package C 36 | - _technically_, D doesn't need to be exported, but that not what 37 | the majority of users would expect 38 | - I must be a symbol designator 39 | - IDK what else could be said about this one... 40 | 41 | 42 | (declaim A) 43 | (defun B (...) 44 | (declare C)) 45 | - A is very probably a declaration about B 46 | - e.g. inline, ftype, optimization 47 | - C is probably a declaration about B's arguments 48 | 49 | fiind-sym 50 | => should suggest find-symbol 51 | 52 | |# 53 | 54 | (defpackage #:breeze.completion 55 | (:documentation "Commands to complete text") 56 | (:use #:cl) 57 | (:import-from #:breeze.command 58 | #:define-command 59 | #:current-buffer 60 | #:return-value-from-command) 61 | (:import-from #:breeze.parser 62 | #:node-iterator) 63 | (:export #:completions-at-point)) 64 | 65 | (in-package #:breeze.completion) 66 | 67 | ;; TODO this shouldn't show up in the list of commands suggested by 68 | ;; "quickfix", and it shouldn't generate an interactive command when 69 | (define-command completions-at-point (&optional string) 70 | "completion-at-point" 71 | (declare (ignorable string)) 72 | (let* (($node (node-iterator (current-buffer))) 73 | (node (breeze.iterator:value $node))) 74 | (declare (ignorable node)) 75 | ;; (break "~s" (breeze.parser:node-string $node)) 76 | (return-value-from-command 77 | (list "prin1" "print") 78 | #++ 79 | (when (breeze.parser:token-node-p node) 80 | 81 | '("asfd" "qwer" "uiop"))))) 82 | 83 | ;; find-most-similar-symbol 84 | 85 | #| 86 | print 87 | 88 | (breeze.listener::find-most-similar-symbol "make-node-iterator") 89 | |# 90 | -------------------------------------------------------------------------------- /src/cmds/project.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.project 2 | (:documentation "Project scaffolding utilities") 3 | (:use #:cl) 4 | (:import-from #:breeze.utils 5 | #:length>1?) 6 | (:import-from #:breeze.command 7 | #:read-string 8 | #:ask-y-or-n-p 9 | #:return-from-command 10 | #:choose) 11 | (:export #:project 12 | #:confirm-scaffold-directory 13 | #:choose-local-project-directories)) 14 | 15 | (in-package #:breeze.project) 16 | 17 | (defclass project () 18 | ((name 19 | :initform nil 20 | :initarg :name 21 | :accessor name 22 | :documentation "Name of the project,") 23 | (root-dir 24 | :initform nil 25 | :initarg :root-dir 26 | :accessor root-dir 27 | :documentation "The path of the root directory of the project."))) 28 | 29 | ;; &key depends-on author include-copyright license 30 | ;; name template-directory template-parameters 31 | ;; &allow-other-keys 32 | 33 | (defun ql-local-project-directories () 34 | "Get the list of quicklisp local-projects directories (as strings)." 35 | #+quicklisp 36 | (mapcar #'namestring 37 | ql:*local-project-directories*) 38 | ;; To appease sbcl's type checking xD 39 | #-quicklisp (list)) 40 | 41 | #| 42 | 43 | ; in: DEFUN CHOOSE-LOCAL-PROJECT-DIRECTORIES 44 | ; (BREEZE.COMMAND:CHOOSE "Please choose where to create the project: " 45 | ; BREEZE.PROJECT::DIRECTORIES) 46 | ; 47 | ; note: deleting unreachable code 48 | 49 | ; (FIRST BREEZE.PROJECT::DIRECTORIES) 50 | ; 51 | ; note: deleting unreachable code 52 | 53 | |# 54 | 55 | (defun choose-local-project-directories () 56 | (let ((directories (ql-local-project-directories))) 57 | (cond 58 | ((null directories) 59 | (read-string 60 | "Please enter the directory where to create the project: ")) 61 | ((length>1? directories) 62 | (choose "Please choose where to create the project: " 63 | directories)) 64 | (t 65 | (first directories))))) 66 | 67 | (defun confirm-scaffold-directory (directory) 68 | "If a user choose to scaffold a project into a directory that already 69 | exists, we need their confirmation to continue." 70 | (when (probe-file directory) 71 | (unless (ask-y-or-n-p 72 | "The directory \"~A\" already exists. Scaffolding might result in data loss, are you sure you want to continue? (y/n) " 73 | directory) 74 | (return-from-command))) 75 | directory) 76 | 77 | 78 | 79 | #| 80 | "system.asd" 81 | "readme.org" 82 | "package.lisp" 83 | ("test.lisp" 84 | "(defpackage #:.test 85 | (:documentation \"Tests for the package \") 86 | (:use #:cl)) 87 | 88 | (in-package #:loom.test)") 89 | "application.lisp" 90 | "package.lisp" 91 | 92 | |# 93 | -------------------------------------------------------------------------------- /tests/dummy-package.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (uiop:define-package #:breeze.dummy.test 4 | (:mix #| :breeze.definition |# :cl) 5 | (:nicknames :dum) 6 | (:export 7 | ;; Documented symbols 8 | #:*bound-variable* 9 | #:*unbound-variable* 10 | #:a-class 11 | #:a-function 12 | #:a-generic-function 13 | #:a-macro 14 | #:slot 15 | #:an-integer 16 | 17 | ;; Undocumented symbols 18 | #:*bound-variable-undocumented* 19 | #:*unbound-variable-undocumented* 20 | #:class-undocumented 21 | #:function-undocumented 22 | #:generic-function-undocumented 23 | #:macro-undocumented 24 | #:slot-undocumented 25 | #:integer-undocumented 26 | 27 | #:another-generic-function 28 | 29 | ;; 30 | #:mul 31 | #:2x 32 | #:add-one)) 33 | 34 | (in-package #:breeze.dummy.test) 35 | 36 | 37 | ;;; Documented symbols 38 | 39 | (defvar *unbound-variable* () 40 | "A documented unbound symbol.") 41 | 42 | (defvar *bound-variable* t 43 | "A documented bound symbol.") 44 | 45 | (defun a-function () 46 | "A documented function." 47 | t) 48 | 49 | (defgeneric a-generic-function () 50 | (:documentation "A documented generic function.")) 51 | 52 | (defmethod a-generic-function () 53 | "A method." 54 | t) 55 | 56 | (defmacro a-macro () 57 | "A documented macro." 58 | t) 59 | 60 | (defclass a-class () 61 | ((slot 62 | :accessor slot 63 | :documentation "A documented slot.")) 64 | (:documentation "A documented class.")) 65 | 66 | (deftype an-integer () "A documented type-specifier." '(integer 0 100)) 67 | 68 | 69 | ;;; Undocumented symbols 70 | 71 | (defvar *unbound-variable-undocumented* ()) 72 | (defvar *bound-variable-undocumented* t) 73 | (defun function-undocumented ()) 74 | (defgeneric generic-function-undocumented ()) 75 | (defmethod generic-function-undocumented ()) 76 | (defmacro macro-documented ()) 77 | (defclass class-undocumented () 78 | ((slot-undocumented 79 | :accessor slot-undocumented))) 80 | 81 | (deftype integer-undocumented () '(integer 0 100)) 82 | 83 | 84 | ;;; Other cases 85 | 86 | (defgeneric another-generic-function (x)) 87 | (defmethod another-generic-function ((x (eql '1)))) 88 | (defmethod another-generic-function ((x (eql '2))) 89 | "documented" t) 90 | 91 | 92 | ;;; 93 | 94 | (defun mul (x y) 95 | "Multiply x by y." 96 | (* x y)) 97 | 98 | (defun 2x (x) 99 | "Multiply x by 2." 100 | (mul 2 x)) 101 | 102 | (defun add-one (x) 103 | "Add 1 to x." 104 | (1+ x)) 105 | 106 | #+ (or) 107 | (deftest mul 108 | (is (= 4 (mul 2 2))) 109 | (is (= 12 (mul 2 6)))) 110 | 111 | #+ (or) 112 | (deftest 2x 113 | (is (= (2x 2) (mul 2 2)))) 114 | 115 | #+ (or) 116 | (deftest should-fail 117 | (is (= 6 (mul 2 2)))) 118 | -------------------------------------------------------------------------------- /scratch-files/indentation.lisp: -------------------------------------------------------------------------------- 1 | 2 | ;;; Documentations 3 | 4 | ;; https://joaotavora.github.io/sly/#Semantic-indentation 5 | ;; https://slime.common-lisp.dev/doc/html/Semantic-indentation.html#Semantic-indentation 6 | ;; Info node `(elisp) Indenting Macros' 7 | 8 | 9 | ;;; Trivial-indent common lisp library 10 | 11 | ;; trivial-indent "just" configures slynk or swank 12 | (ql:quickload "trivial-indent") 13 | 14 | (trivial-indent:define-indentation defmacro (4 &lambda &body)) 15 | (trivial-indent:define-indentation something-more-complex (4 &rest (&whole 2 0 4 &body))) 16 | 17 | ;; what exactly does "trivial-indent:define-indentation" updates? 18 | ;; How does it relate to the "slime-cl-indent" contrib? 19 | ;; 20 | ;; Ah! "swank-indentation" package is defined in `slime/contrib/swank-indentation.lisp' 21 | ;; And it is indeed the "back-end" part of the "slime-cl-indent" contrib. 22 | ;; 23 | ;; it calls `update-indentation-information` in either 24 | ;; "swank-indentation" or "slynk/indentation" package. 25 | 26 | swank-indentation:update-indentation-information 27 | slynk/indentation:update-indentation-information 28 | 29 | ;; `trivial-indent:initialize-slime' and 30 | ;; `trivial-indent:initialize-sly' sets the variables 31 | ;; `swank-indentation:*application-hints-tables*' and 32 | ;; `slynk/indentation:*application-hints-tables*' 33 | 34 | ;; it does keep a hash-table *indentation-hints* 35 | 36 | 37 | ;;; slime-cl-indent elisp slime contrib 38 | 39 | ;; ‘slime-cl-indent.el’ 40 | ;; elisp variables 41 | ;; - common-lisp-style 42 | ;; - common-lisp-styles 43 | ;; macro 44 | ;; `define-common-lisp-style' 45 | 46 | ;; slime-cl-indent "just" configures emacs' `lisp-mode' 47 | 48 | 49 | ;;; emacs' built-in `lisp-mode' 50 | 51 | ;; ‘lisp-mode.el’ defines a bunch of variables to control the indentation: 52 | 53 | ( 54 | lisp-align-keywords-in-calls 55 | lisp-backquote-indentation 56 | lisp-indent-defun-method 57 | lisp-indent-maximum-backtracking 58 | lisp-lambda-list-indentation 59 | lisp-lambda-list-keyword-alignment 60 | lisp-lambda-list-keyword-parameter-alignment 61 | lisp-lambda-list-keyword-parameter-indentation 62 | lisp-loop-body-forms-indentation 63 | lisp-loop-clauses-indentation 64 | lisp-loop-indent-body-forms-relative-to-loop-start 65 | lisp-loop-indent-forms-like-keywords 66 | lisp-loop-indent-subclauses 67 | lisp-simple-loop-indentation 68 | lisp-tag-body-indentation 69 | lisp-tag-indentation 70 | ) 71 | 72 | interactive function: `indent-sexp' 73 | 74 | 75 | ;;; emacs' built-in syntax.el 76 | 77 | `parse-partial-sexp' 78 | `syntax-ppss' ;; Parse Partial Sexp State 79 | 80 | ;; parse-partial-sexp is a primitive-function in ‘src/syntax.c’. 81 | 82 | 83 | 84 | ;;; Examples that I want to work better 85 | 86 | ;; @ is where the cursor/point is at 87 | 88 | ;; paredit-reindent-defun does nothing is this case, but indent-sexp works 89 | (defun f (x) 90 | x) 91 | -------------------------------------------------------------------------------- /tests/emacs/wip-demo.el: -------------------------------------------------------------------------------- 1 | 2 | (defmacro breeze/demo/with-read-string (values &rest body) 3 | "Macro to help mock the function read-string" 4 | (let ((values-var (gensym))) 5 | `(let ((,values-var ,values)) 6 | (cl-letf (((symbol-function 'read-string) 7 | (lambda (prompt &optional initial-input default-value) 8 | (pop ,values-var)))) 9 | ,@body)))) 10 | 11 | (defun breeze/demo/find-sldb-buffer () 12 | (remove-if-not 13 | #'(lambda (buffer-name) 14 | (string-match-p "^\\*sldb" buffer-name)) 15 | (mapcar #'buffer-name 16 | (buffer-list)))) 17 | 18 | ;; (mapcar #'kill-buffer (breeze/demo/find-sldb-buffer)) 19 | 20 | (use-package htmlize) 21 | 22 | (with-current-buffer (get-buffer-create "demo.lisp") 23 | (erase-buffer) 24 | (lisp-mode) 25 | (slime-mode) 26 | (breeze-mode) 27 | ;; Use breeze's command to insert a package definition 28 | (breeze/demo/with-read-string 29 | '("demo" "") 30 | (breeze-insert-defpackage)) 31 | ;; Use breeze's command to insert a function 32 | (breeze/demo/with-read-string 33 | '("foo" "") 34 | (breeze-insert-defun)) 35 | ;; Write the function's body 36 | (insert "42") 37 | ;; Go "out" of the function's body 38 | (end-of-buffer) 39 | (insert "\n\n") 40 | 41 | ;; Evaluate the whole buffer 42 | (slime-eval-buffer) 43 | 44 | ;; Try to call "foo", but make a typo 45 | (insert "(fop)") 46 | ;; Take """screenshot""" of slime's debugger buffer. 47 | (run-at-time "0.5 sec" nil 48 | ;; TODO Extract this in another function 49 | #'(lambda () 50 | (with-temp-buffer 51 | ;; Copy SLDB's buffer into the temp buffer 52 | (insert 53 | (with-current-buffer 54 | (get-buffer 55 | ;; Open the first SLDB buffer 56 | (car (breeze/demo/find-sldb-buffer))) 57 | (prog1 58 | (buffer-string) 59 | ;; Abort the evaluation 60 | (sldb-invoke-restart-by-name "ABORT")))) 61 | ;; Export the temp buffer into a new "html" buffer 62 | (let ((html-buffer (htmlize-buffer))) 63 | (with-current-buffer html-buffer 64 | ;; Write the html result 65 | (write-file (breeze/demo/next-to-this-file "demo.html")) 66 | ;; Kill the html buffer 67 | (kill-buffer)))))) 68 | (slime-eval-last-expression) 69 | (write-file (breeze/demo/next-to-this-file "demo.lisp")) 70 | ;; (buffer-substring-no-properties (point-min) (point-max)) 71 | ) 72 | 73 | 74 | ;; NEXT STEP: take the html buffer and extract the CSS and the PRE 75 | ;; element so we can embed the result into another webpage, like 76 | ;; breeze's documentation 77 | -------------------------------------------------------------------------------- /docs/design_decisions.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 14d42b3a-0a2f-4a3b-8937-7175e621c6ec 3 | :END: 4 | #+title: Design Decisions 5 | 6 | * Design decisions 7 | 8 | ** Write everything in common lisp 9 | 10 | As much as possible, so that breeze can easily be ported to different 11 | platforms and editors. 12 | 13 | ** Wrap definitions :obsolete: 14 | 15 | Decision: Create wrapper macros (e.g. =br:defun=) to keep the original 16 | forms for later analysis. 17 | 18 | This decision is really not definitive. 19 | 20 | This decision is less than ideal, especially for existing systems, but 21 | it was the easiest to start with. 22 | 23 | *** Alternatives 24 | 25 | **** Keep the string being eval'd 26 | 27 | Advising swank's eval function is "a good start" in that direction. 28 | 29 | **** Parse the source code 30 | 31 | - Might be hard, but [[https://github.com/s-expressionists/Eclector][eclector]] could make this easy. 32 | - [[https://github.com/hyotang666/read-as-string][hyotang666/read-as-string]] is another candidate 33 | 34 | ** Migrate to parachute 2022-03-08 35 | 36 | The test framework and the "wrap definition" parts always were 37 | proof-of-concepts: I wanted to be able to define some tests, and run 38 | them when either the test of the system-under-test was redefined. It 39 | worked, but now that I have a more and more complete common lisp 40 | parser, I can do the things properly. So I've move the concerned code 41 | into the folder "scratch-files" and I'll re-introduce them slowly in 42 | the future. (Because I really want something to run the tests in the 43 | background, for example.) 44 | 45 | ** Read from strings instead of streams 46 | 47 | I did some tests and the code was like 100x faster when reading from 48 | string instead of reading from streams. There are multiple reasons: to 49 | extract the "raw" text from the stream require consing new strings 50 | _and_ abusing file-position to move back and forth in the stream, both 51 | of these are very inefficient. Instead, we use displaced arrays which 52 | results in way less consing and no "stream state" to manage. This made 53 | both the code faster and simpler. 54 | 55 | From another point of view: why not? we were already copying the whole 56 | stream into the resulting tree, now we just have references to one 57 | string. 58 | 59 | ** Use =licence= and not =license= 60 | 61 | This is a very tiny decision, but I know I'll forget it. 62 | 63 | What made me decide between the two: =licence= is what asdf use, and 64 | it's what the user will see in their projects. 65 | 66 | ** Only use dependencies from quicklisp's distribution 67 | 68 | This project is not in quicklisp, and I don't plan to add it to 69 | quicklisp until it stabilizes (which might take years). I make sure to 70 | only use dependencies from quicklisp so that if somebody wants to try 71 | it out they'll just need to clone this repository in quicklisp's 72 | local-projects folder. 73 | -------------------------------------------------------------------------------- /docs/eclector.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 8e956afc-28f4-4fda-b58f-7c111b0d92b3 3 | :END: 4 | #+title: Eclector 5 | #+filetags: :reader: 6 | 7 | [[https://github.com/s-expressionists/Eclector][Eclector on github]] 8 | 9 | #+begin_quote 10 | A portable Common Lisp reader that is highly customizable, can recover 11 | from errors and can return concrete syntax trees 12 | #+end_quote 13 | 14 | * Why not use eclector? 15 | :PROPERTIES: 16 | :ID: 08754391-271f-4e05-b307-3c912f5f4c0a 17 | :END: 18 | 19 | Simply because eclector was not mature enough back when I tried to use 20 | it. It had a few updates since then and it might work well now; at 21 | least the actual reader. I don't know if the CST (Concrete Syntax 22 | Tree) that it provides would be appropriate all the use cases. 23 | 24 | In any case, nothing prevents one to use _both_ reader. Breeze's 25 | reader is meant to be robust and safe, maybe it would make sense to 26 | use breeze's parser as a first pass, as a validation, and if 27 | everything goes well continue with eclector's reader. 28 | 29 | * Differences between eclector and breeze's parser 30 | :PROPERTIES: 31 | :ID: 979f0424-ad37-42ee-a1e6-99135fedc6da 32 | :END: 33 | 34 | Note: eclector's reader is highly customizable, so I'm trying to 35 | qualify eclector's default reader. *Also*, I haven't tried eclector in 36 | a few years, it's highly probable that something changed since. 37 | 38 | +---------------------------------+--------------------------------------------------------+ 39 | | breeze's parser | eclector's reader | 40 | +---------------------------------+--------------------------------------------------------+ 41 | | meant to be used by an editor | meant to implement ~cl:read~ | 42 | +---------------------------------+--------------------------------------------------------+ 43 | | is not customizable | is highly customizable | 44 | +---------------------------------+--------------------------------------------------------+ 45 | | doesn't discard any information | discard all whitespaces, comments and | 46 | | | feature expressions that fails (e.g. ~#+(or) this~) | 47 | +---------------------------------+--------------------------------------------------------+ 48 | | will never singal an error | signals error and provide restarts to let the client | 49 | | (unless there's a bug!) | decide how to handle the situation | 50 | +---------------------------------+--------------------------------------------------------+ 51 | 52 | * TODO Try out the "new" ~skipped-input-recording-client~ 53 | :PROPERTIES: 54 | :ID: 03eebc77-bf6a-4172-8496-4dfb2490c01b 55 | :END: 56 | 57 | #+begin_src lisp 58 | (eclector.parse-result:read 59 | (make-instance 'skipped-input-recording-client) 60 | stream nil :eof) 61 | #+end_src 62 | -------------------------------------------------------------------------------- /docs/features.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 13ea055e-4715-4583-811b-bff78ca300ee 3 | :END: 4 | #+title: Candidate Features 5 | 6 | * See also 7 | 8 | - [[id:5d211d9a-0749-4adb-abe0-e66133d09b5b][Editor integrations]] 9 | - [[id:54e6cd55-803b-4e15-82bc-a332130d020e][Sly/Slime integration]] 10 | 11 | * TODO Integration with occur :ux:emacs: 12 | :PROPERTIES: 13 | :ID: eb1f399a-e723-4a5d-91bd-26b473faca5d 14 | :END: 15 | 16 | - Make breeze's command "work" in occur's buffer 17 | - e.g. eval this (with the right package), or M-. 18 | 19 | * TODO Integration with which-key :ux:emacs: 20 | :PROPERTIES: 21 | :ID: a91ccca0-d156-4bb3-835d-aecaff9ca886 22 | :END: 23 | 24 | https://github.com/justbur/emacs-which-key/ 25 | 26 | #+begin_quote 27 | Emacs package that displays available keybindings in popup 28 | #+end_quote 29 | 30 | It is possible to use 31 | e.g. =which-key-add-major-mode-key-based-replacements= to configure 32 | =which-key= to show more user-friendly names for the commands bound to 33 | each keys. 34 | 35 | * TODO Integration with treemacs :ux:emacs: 36 | :PROPERTIES: 37 | :ID: 71f02e53-2520-49f4-866c-40f146712db8 38 | :END: 39 | 40 | https://github.com/Alexander-Miller/treemacs 41 | 42 | #+begin_quote 43 | a tree layout file explorer for Emacs 44 | #+end_quote 45 | 46 | It is possible to use treemacs as "just" a treeview widget. Here's an 47 | example: https://github.com/emacs-lsp/lsp-treemacs 48 | 49 | * TODO Integration with origami.el :ux:emacs: 50 | :PROPERTIES: 51 | :ID: 87cb103c-906a-46d8-8f90-3b86668bc667 52 | :END: 53 | 54 | https://github.com/gregsexton/origami.el 55 | 56 | #+begin_example 57 | A folding minor mode for Emacs 58 | #+end_example 59 | 60 | #+begin_quote 61 | An origami parser is a function that takes a 'create function' and 62 | returns a function taking the string to be parsed. The returned 63 | function should return a list of fold nodes. Fold nodes are created 64 | using the passed-in create function. 65 | #+end_quote 66 | 67 | Note sure if it would be useful, I assume the built-in support for 68 | lisp is already good. 69 | 70 | * TODO Integration with flycheck :ux:emacs: 71 | :PROPERTIES: 72 | :ID: ebbcbf1f-da36-4064-8c22-addd2dc6c664 73 | :END: 74 | 75 | https://www.flycheck.org 76 | 77 | - [[https://www.flycheck.org/en/latest/user/flycheck-versus-flymake.html][Flycheck vs Flymake]] 78 | - [[https://www.flycheck.org/en/latest/developer/developing.html][Adding a syntax checker to Flycheck]] 79 | - Probably want to use ~flycheck-define-generic-checker~ 80 | 81 | Note about autoloading: 82 | 83 | #+begin_quote 84 | The flycheck-define-checker macro is an autoload, so using it inside a 85 | with-eval-after-load form will load all of Flycheck. While this 86 | ensures the macro is correctly expanded, it also defeats the purpose 87 | of using with-eval-after-load. 88 | #+end_quote 89 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | ### Base layers, setup working directory and quicklisp 3 | # FROM docker.io/clfoundation/${LISP}:${LISP_VERSION} as base 4 | FROM alpine:3.18.4 AS base 5 | 6 | RUN mkdir /breeze 7 | WORKDIR /breeze 8 | 9 | 10 | FROM base AS quicklisp 11 | 12 | RUN apk add sbcl 13 | COPY scripts/quicklisp.lisp scripts/quicklisp.lisp 14 | RUN sbcl --non-interactive \ 15 | --load scripts/quicklisp.lisp \ 16 | --eval "(quicklisp-quickstart:install)" \ 17 | --eval "(ql-util:without-prompting (ql:add-to-init-file))" 18 | 19 | ###################################################################### 20 | ### Download all needed dependencies (for the main and the test 21 | ### systems). 22 | FROM quicklisp AS deps 23 | 24 | COPY breeze.asd . 25 | COPY scripts/load-dependencies.lisp scripts/load-dependencies.lisp 26 | 27 | RUN sbcl --noinform --non-interactive \ 28 | --load scripts/load-dependencies.lisp 29 | 30 | 31 | FROM scratch AS dependencies.core 32 | 33 | COPY --from=deps /breeze/dependencies.core /dependencies.core 34 | 35 | ###################################################################### 36 | ### Run the tests and generate some documentation 37 | FROM quicklisp AS test 38 | 39 | COPY . . 40 | RUN sbcl --core dependencies.core \ 41 | --eval "(asdf:test-system '#:breeze)" 42 | 43 | FROM base AS org-publish 44 | 45 | RUN apk add bash ca-certificates emacs 46 | 47 | COPY . . 48 | COPY --from=test /breeze/docs /breeze/docs 49 | 50 | RUN emacs -Q --batch --load scripts/org-publish-project.el --kill 51 | RUN ls 52 | RUN ls /breeze/public 53 | 54 | FROM scratch AS public 55 | 56 | COPY --from=org-publish /breeze/public / 57 | 58 | 59 | ###################################################################### 60 | ### This is where I left off 61 | 62 | FROM deps AS integration-tests-base 63 | 64 | RUN apk add bash ca-certificates emacs 65 | RUN apk add scrot screen ffmpeg xvfb-run 66 | RUN sbcl --noinform --non-interactive --eval "(ql:quickload '#:swank)" 67 | 68 | COPY . . 69 | 70 | FROM integration-tests-base AS debug 71 | 72 | # RUN apk add x11vnc 73 | RUN echo screen -ls >> ~/.bash_history 74 | RUN echo /breeze/scripts/demo/demo-recorder.sh >> ~/.bash_history 75 | RUN echo screen -R >> ~/.bash_history 76 | 77 | FROM integration-tests-base AS integration-tests-run 78 | 79 | # TODO 80 | # Run some test (fails because slime is not started) 81 | # RUN emacs -batch -l ert -l /breeze/tests/emacs/breeze-test.el -f ert-run-tests-batch-and-exit 82 | 83 | # TODO emacs (x11) complains that stdin is not a tty, might need to run it under screen 84 | # 85 | # -t file, --terminal=file 86 | # Use specified file as the terminal instead of using stdin/stdout. This must be the first argument specified in the command line. 87 | # 88 | # Run the demo 89 | RUN scripts/demo/demo-recorder.sh 90 | 91 | # Copy only the outputs 92 | FROM scratch AS integration-tests 93 | COPY --from=integration-tests-run /breeze/scripts/demo/output/* / 94 | 95 | # for image in $(docker exec breeze-demo-recorder sh -c 'ls /breeze/scripts/demo/*.png'); do 96 | # name=$(basename $image) 97 | # docker cp breeze-demo-recorder:$image $output/$name 98 | # done 99 | -------------------------------------------------------------------------------- /tests/emacs/breeze-test.el: -------------------------------------------------------------------------------- 1 | 2 | (require 'ert) 3 | 4 | (defun breeze--xor (a b) 5 | (or (and a (not b)) 6 | (and (not a) b))) 7 | 8 | (ert-deftest test/breeze--xor () 9 | (should (equal '(nil t t nil) 10 | (mapcar (lambda (args) 11 | (apply 'breeze--xor args)) 12 | '((nil nil) 13 | (nil t) 14 | (t nil) 15 | (t t)))))) 16 | 17 | 18 | 19 | (ert-deftest test/breeze-%symbolicate () 20 | (should (eq 'sly (breeze-%symbolicate2 "sly"))) 21 | (should (eq 'sly (breeze-%symbolicate2 'sly))) 22 | (should (eq 'slime (breeze-%symbolicate2 "slime"))) 23 | (should (eq 'slime (breeze-%symbolicate2 'slime))) 24 | (should (eq 'sly-eval (breeze-%symbolicate2 'sly "eval"))) 25 | (should (eq 'slime-eval (breeze-%symbolicate2 'slime "eval"))) 26 | (should (eq 'slime-connected-hook 27 | (breeze-%symbolicate2 'slime "connected-hook")))) 28 | 29 | 30 | 31 | ;; TODO true only if connected! 32 | (ert-deftest test/breeze-connection () 33 | (should (breeze--xor 34 | (breeze-sly-connected-p) 35 | (breeze-slime-connected-p))) 36 | (should (eq t (breeze-check-if-connected-to-listener)))) 37 | 38 | (ert-deftest test/breeze-eval () 39 | ;; Integers 40 | (should (= (breeze-eval "(+ 1 2)") 3)) 41 | ;; Strings 42 | 43 | (should (string= (breeze-eval "\"hi\"") "hi")) 44 | ;; Symbols 45 | (should (eq (breeze-eval "cl:t") t)) 46 | (should (eq (breeze-eval "cl:nil") nil)) 47 | (should (eq t (breeze-eval-predicate "t"))) 48 | (should (eq t (breeze-eval-predicate "T"))) 49 | (should (eq nil (breeze-eval-predicate "nil"))) 50 | (should (eq nil (breeze-eval-predicate "NIL")))) 51 | 52 | ;; TODO Figure out how to evaluate something without triggering the debugger when an error occurs 53 | ;; (ert-deftest breeze-eval-empty-string () 54 | ;; :expected-result :failed 55 | ;; (breeze-eval "")) 56 | 57 | ;; (let ((slime-event-hooks (list (lambda (event) 58 | ;; (message "Event: %S" (list (car event) 59 | ;; (length (cdr event)))) 60 | ;; nil)))) 61 | ;; (breeze-eval "(error \"oups\")")) 62 | ;; (breeze-eval "(read)") 63 | 64 | 65 | 66 | 67 | (ert-deftest test/breeze-relative-path () 68 | (should (file-exists-p (breeze-relative-path))) 69 | (should (file-exists-p (breeze-relative-path "src/"))) 70 | (should (file-exists-p (breeze-relative-path "src/breeze.el"))) 71 | (should (file-exists-p (breeze-relative-path "src/ensure-breeze.lisp")))) 72 | 73 | (ert-deftest test/breeze-init () 74 | (should (eq t (breeze-validate-if-package-exists "CL"))) 75 | (should (eq nil (breeze-validate-if-package-exists "this package probably doesn't exists")))) 76 | ;; t 77 | 78 | 79 | ;; TODO only after (breeze-ensure) 80 | ;; (should (eq t (breeze-validate-if-breeze-package-exists))) 81 | 82 | ;; (should (eq t (breeze-ensure))) 83 | 84 | 85 | 86 | (ert-deftest test/breeze-intergration () 87 | (ert-test-erts-file "breeze.erts")) 88 | 89 | 90 | ;; TODO (ert-deftest test/generate-bindings-documentation ) 91 | 92 | ;; TODO (ert-deftest test/update-list-of-stubs) 93 | -------------------------------------------------------------------------------- /tests/pattern/compile-pattern.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:breeze.test.pattern) 2 | 3 | 4 | 5 | (define-test+run symbol-starts-with 6 | (progn 7 | (false (symbol-starts-with 'x #\?)) 8 | (true (symbol-starts-with :? #\?)) 9 | (true (symbol-starts-with :?x #\?)) 10 | (true (symbol-starts-with '? #\?)) 11 | (true (symbol-starts-with '?x #\?))) 12 | (progn 13 | (false (symbol-starts-with 'x "?")) 14 | (true (symbol-starts-with :? "?")) 15 | (true (symbol-starts-with :?x "?")) 16 | (true (symbol-starts-with '? "?")) 17 | (true (symbol-starts-with '?x "?")))) 18 | 19 | (define-test+run var-symbol-p 20 | (true (var-symbol-p :?x)) 21 | (false (var-symbol-p 'x)) 22 | (false (var-symbol-p "?x"))) 23 | 24 | (define-test+run multi-valued-var-symbol-p 25 | (true (multi-valued-var-symbol-p :?*x)) 26 | (false (multi-valued-var-symbol-p :?x)) 27 | (false (multi-valued-var-symbol-p 'x)) 28 | (false (multi-valued-var-symbol-p "?x"))) 29 | 30 | (define-test+run wildcard-symbol-p 31 | (true (wildcard-symbol-p :_x)) 32 | (false (wildcard-symbol-p 'x)) 33 | (false (wildcard-symbol-p "_x"))) 34 | 35 | 36 | 37 | (define-test+run "compile-pattern - wildcard" 38 | (is eqv (wildcard) (compile-pattern :_x)) 39 | (is eqv (wildcard) (compile-pattern :_)) 40 | (is eqv (wildcard) (compile-pattern '_))) 41 | 42 | (define-test+run "compile-pattern - atoms" 43 | (is eqv :x (compile-pattern :x)) 44 | (is eqv (svar :?x) (compile-pattern :?x)) 45 | (is eqv (svar '?x) (compile-pattern '?x)) 46 | (is eqv 47 | (svar '?*x :multi-valued-p t) 48 | (compile-pattern '?*x)) 49 | (is eqv 42 (compile-pattern 42)) 50 | (is eqv "abc" (compile-pattern "abc"))) 51 | 52 | (define-test+run "compile-pattern - :var" 53 | (is eqv (var :?y (wildcard)) (compile-pattern '(:var :?y _))) 54 | (is eqv (var :?z (svar '?a)) (compile-pattern '(:var :?z ?a))) 55 | (let ((p (compile-pattern '(?x ?x)))) 56 | (is eq (name (aref p 0)) (name (aref p 1))))) 57 | 58 | (define-test+run "compile-pattern - :maybe" 59 | (is eqv (maybe :x) (compile-pattern '(:maybe :x))) 60 | (is eqv (maybe #(:x :y)) (compile-pattern '(:maybe (:x :y)))) 61 | (is eqv (maybe :x :?y) (compile-pattern '(:maybe :x :?y))) 62 | (is eqv (maybe :x :?z) (compile-pattern '(:named :?z (:maybe :x)))) 63 | (is eqv (maybe (svar :?x)) (compile-pattern '(:maybe :?x))) 64 | (is eqv (maybe (var :?x (wildcard))) (compile-pattern '(:maybe (:var :?x :_))))) 65 | 66 | (define-test+run "compile-pattern - :zero-or-more" 67 | (is eqv (zero-or-more #(:x)) (compile-pattern '(:zero-or-more :x))) 68 | (is eqv (zero-or-more #(:x :y)) (compile-pattern '(:zero-or-more :x :y)))) 69 | 70 | (define-test+run "compile-pattern - :either" 71 | (is eqv (either #(:x)) (compile-pattern '(:either :x))) 72 | (is eqv (either #(:x :y)) (compile-pattern '(:either :x :y)))) 73 | 74 | (define-test+run "compile-pattern - :symbol" 75 | (is eqv (sym :wild :wild :wild) (compile-pattern '(:symbol))) 76 | (is eqv (sym :wild :defun :wild) (compile-pattern '(:symbol :defun))) 77 | (is eqv (sym :cl :defun :wild) (compile-pattern '(:symbol :defun :cl))) 78 | (is eqv (sym :cl :defun :qualified) (compile-pattern '(:symbol :defun :cl :qualified)))) 79 | 80 | (define-test+run "compile-pattern - vectors" 81 | (is eqv (vector '?xx) (compile-pattern #(?xx)))) 82 | -------------------------------------------------------------------------------- /src/cl-todo.lisp: -------------------------------------------------------------------------------- 1 | ;;; These are the functions left to "classify" in cl.lisp 2 | 3 | LDB 4 | LDB-TEST 5 | 6 | 7 | 8 | 9 | LET 10 | LET* 11 | LISP-IMPLEMENTATION-TYPE 12 | LISP-IMPLEMENTATION-VERSION 13 | LIST 14 | LIST* 15 | 16 | LIST-LENGTH 17 | 18 | LISTP 19 | LOAD 20 | LOAD-LOGICAL-PATHNAME-TRANSLATIONS 21 | LOAD-TIME-VALUE 22 | LOCALLY 23 | LOG 24 | 25 | LOGAND 26 | LOGANDC1 27 | LOGANDC2 28 | LOGBITP 29 | LOGCOUNT 30 | LOGEQV 31 | LOGIOR 32 | LOGNAND 33 | LOGNOR 34 | LOGNOT 35 | LOGORC1 36 | LOGORC2 37 | LOGTEST 38 | LOGXOR 39 | 40 | LONG-SITE-NAME 41 | LOOP 42 | LOOP-FINISH 43 | LOWER-CASE-P 44 | MACHINE-INSTANCE 45 | MACHINE-TYPE 46 | MACHINE-VERSION 47 | MACRO-FUNCTION 48 | MACROEXPAND 49 | MACROEXPAND-1 50 | MACROLET 51 | MAKE-ARRAY 52 | 53 | 54 | MAKE-CONDITION 55 | 56 | 57 | 58 | MAKE-HASH-TABLE 59 | MAKE-INSTANCE 60 | MAKE-INSTANCES-OBSOLETE 61 | MAKE-LIST 62 | MAKE-LOAD-FORM 63 | MAKE-LOAD-FORM-SAVING-SLOTS 64 | 65 | MAKE-PATHNAME 66 | MAKE-RANDOM-STATE 67 | 68 | MAKE-STRING 69 | 70 | 71 | MAP 72 | MAP-INTO 73 | MAPC 74 | MAPCAN 75 | MAPCAR 76 | MAPCON 77 | MAPHASH 78 | MAPL 79 | MAPLIST 80 | 81 | MASK-FIELD 82 | 83 | MEMBER 84 | MEMBER-IF 85 | MEMBER-IF-NOT 86 | MERGE 87 | 88 | SET-PPRINT-DISPATCH 89 | COPY-PPRINT-DISPATCH 90 | 91 | 92 | GET-PROPERTIES 93 | 94 | EVERY 95 | SOME 96 | 97 | ENOUGH-NAMESTRING 98 | ENSURE-DIRECTORIES-EXIST 99 | 100 | 101 | (EQ EQL EQUAL EQUALP) 102 | 103 | ERROR 104 | ETYPECASE 105 | (EVAL EVAL-WHEN) 106 | 107 | 108 | IDENTITY 109 | 110 | IMPORT 111 | IN-PACKAGE 112 | 113 | INSPECT 114 | DOCUMENTATION 115 | DISASSEMBLE 116 | DESCRIBE 117 | DESCRIBE-OBJECT 118 | 119 | FILL 120 | FIND 121 | FIND-ALL-SYMBOLS 122 | FIND-CLASS 123 | FIND-IF 124 | FIND-IF-NOT 125 | FIND-METHOD 126 | FIND-PACKAGE 127 | 128 | FIND-SYMBOL 129 | FINISH-OUTPUT 130 | 131 | CONSTANTP 132 | DEFCONSTANT 133 | 134 | COPY-ALIST 135 | COPY-LIST 136 | COPY-SEQ 137 | COPY-STRUCTURE 138 | COPY-SYMBOL 139 | 140 | ROTATEF 141 | SETQ 142 | 143 | ("Progs!" PROG PROG* PROG1 PROG2 PROGN PROGV) 144 | 145 | NAMESTRING 146 | 147 | NOT 148 | NOTANY 149 | NOTEVERY 150 | NRECONC 151 | NREVERSE 152 | 153 | QUOTE 154 | 155 | 156 | 157 | (READ READ-BYTE READ-CHAR READ-CHAR-NO-HANG READ-DELIMITED-LIST READ-FROM-STRING READ-LINE READ-PRESERVING-WHITESPACE READ-SEQUENCE) 158 | 159 | REMPROP 160 | 161 | (REQUIRE PROVIDE) 162 | 163 | SET 164 | 165 | SHADOW 166 | SHADOWING-IMPORT 167 | SHARED-INITIALIZE 168 | SHIFTF 169 | SHORT-SITE-NAME 170 | 171 | SIGNUM 172 | SIMPLE-BIT-VECTOR-P 173 | SIMPLE-CONDITION-FORMAT-ARGUMENTS 174 | SIMPLE-CONDITION-FORMAT-CONTROL 175 | 176 | 177 | SIMPLE-VECTOR-P 178 | 179 | 180 | SLEEP 181 | SLOT-BOUNDP 182 | SLOT-EXISTS-P 183 | SLOT-MAKUNBOUND 184 | SLOT-MISSING 185 | SLOT-UNBOUND 186 | SLOT-VALUE 187 | SOFTWARE-TYPE 188 | SOFTWARE-VERSION 189 | 190 | STABLE-SORT 191 | SORT 192 | 193 | SPECIAL-OPERATOR-P 194 | 195 | 196 | (TIME 197 | (TRACE UNTRACE)) 198 | 199 | 200 | DEFCLASS DEFSTRUCT 201 | 202 | DEFINE-SYMBOL-MACRO 203 | DEFMACRO 204 | 205 | DEFGENERIC DEFMETHOD 206 | DEFINE-METHOD-COMBINATION 207 | 208 | (DEFPARAMETER DEFVAR) 209 | 210 | DESTRUCTURING-BIND 211 | 212 | DIRECTORY DIRECTORY-NAMESTRING 213 | 214 | ((DO DO* DOLIST DOTIMES) 215 | (DO-ALL-SYMBOLS DO-EXTERNAL-SYMBOLS DO-SYMBOLS)) 216 | 217 | DEPOSIT-FIELD 218 | DPB 219 | 220 | DRIBBLE 221 | ED 222 | 223 | ENDP 224 | -------------------------------------------------------------------------------- /docs/reader-macros.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 9cb1f9d0-572f-4b8c-bbc8-4c1bb9a54eb4 3 | :END: 4 | #+title: Reader macros 5 | 6 | https://quickdocs.org/-/search?q=reader 7 | 8 | * Example of a custom reader macro 9 | :PROPERTIES: 10 | :ID: 68e2f3b0-264e-4844-b38f-92be13cca6ea 11 | :END: 12 | 13 | #+begin_src 14 | (with-input-from-string (input "{ hey }") 15 | (let ((*readtable* (copy-readtable))) 16 | (set-macro-character #\{ (lambda (stream char) 17 | (read-delimited-list #\} stream))) 18 | (read input))) 19 | ;; => (HEY) 20 | #+end_src 21 | 22 | * cl-annot 23 | :PROPERTIES: 24 | :ID: 11702123-9dc1-4ca4-9325-53d4ac4188cc 25 | :END: 26 | 27 | =@asfd= 28 | 29 | * Eclector's =syntax-extensions= module 30 | :PROPERTIES: 31 | :ID: 3c68d188-b4c9-4ab1-aeb8-cee25aaa8273 32 | :END: 33 | 34 | From eclector 0.10 release's notes: 35 | 36 | #+begin_src 37 | p::(a b) 38 | === 39 | (p::a p::b) 40 | #+end_src 41 | 42 | #+begin_src 43 | #; (this form is commented out) 44 | #2; (these 2 forms) (are commented out) 45 | #+end_src 46 | 47 | * Named readtables 48 | :PROPERTIES: 49 | :ID: f5fa06ac-75a3-4dbf-8ed3-17c320ff2927 50 | :END: 51 | 52 | #+begin_src 53 | (in-readtable ) 54 | (named-readtables:in-readtable ...) 55 | #+end_src 56 | 57 | * CommonQt/Qtools 58 | :PROPERTIES: 59 | :ID: 16bbdda7-ce07-456b-be44-fd787c712c5f 60 | :END: 61 | 62 | #+begin_src 63 | #_ 64 | q+ 65 | #> #< 66 | #' 67 | #+end_src 68 | 69 | P.S. These are not maintained anymore 70 | 71 | * Library "Reader" 72 | :PROPERTIES: 73 | :ID: 0cddf3d0-b37a-4a66-83dd-05d1e63dea33 74 | :END: 75 | 76 | - https://quickdocs.org/reader 77 | 78 | #+begin_src 79 | (reader:enable-reader-syntax ...) 80 | 81 | #[ 82 | {eq 83 | {eql 84 | {equal 85 | #{ 86 | #! 87 | 88 | ! (not ) 89 | $ "ensure-string" 90 | #+end_src 91 | 92 | * Shebang 93 | :PROPERTIES: 94 | :ID: bc2db964-8402-42e6-8992-dc754941f8c4 95 | :END: 96 | 97 | at least sbcl and roswell 98 | 99 | #+begin_src 100 | #! 101 | #+end_src 102 | 103 | * cl-interpol 104 | :PROPERTIES: 105 | :ID: 1188ce38-45c1-426d-aab1-b4d209baef62 106 | :END: 107 | 108 | #+begin_src 109 | (named-readtables:in-readtable :interpol-syntax) 110 | or 111 | (cl-interpol:enable-interpol-syntax) 112 | (cl-interpol:disable-interpol-syntax) 113 | #+end_src 114 | 115 | #+begin_quote 116 | The question mark may optionally be followed by an R and an X (case 117 | doesn't matter) - see the section about regular expression syntax 118 | below. If both of them are present, the R must precede the X. 119 | #+end_quote 120 | 121 | #+begin_quote 122 | The next character is the opening outer delimiter which may be one of 123 | - ="= (double quote), 124 | - ='= (apostrophe), 125 | - =|= (vertical bar), 126 | - =#= (sharpsign), 127 | - =/= (slash), 128 | - =(= (left parenthesis), 129 | - =<= (less than), 130 | - =[= (left square bracket), or 131 | - ={= (left curly bracket). 132 | (But see =*OUTER-DELIMITERS*=.) 133 | #+end_quote 134 | 135 | * curry-compose-reader-macros 136 | :PROPERTIES: 137 | :ID: eff9b0b6-ceb3-4882-bdec-1ab212fb20fc 138 | :END: 139 | 140 | https://quickdocs.org/curry-compose-reader-macros 141 | 142 | #+begin_src 143 | (in-readtable :curry-compose-reader-macros) 144 | {+ 1} 145 | [#'list {* 2}] 146 | «list {* 2} {* 3}» 147 | ‹if #'evenp #'1+ #'1-› 148 | #+end_src 149 | -------------------------------------------------------------------------------- /src/cmds/test-commands.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.test-commands 2 | (:documentation "Commands related to tests") 3 | (:use #:cl #:breeze.command) 4 | (:export #:run-test-at-point 5 | #:run-tests-in-file 6 | #:run-tests-in-suite 7 | #:run-tests-in-package 8 | #:run-system-tests 9 | #:run-all-tests 10 | #:quickfix-test 11 | #:goto-test 12 | #:move-to-tests 13 | #:undefine-test-at-point)) 14 | 15 | (in-package #:breeze.test-commands) 16 | 17 | #| 18 | 19 | ideas: 20 | - debug tests 21 | - maybe implicitly insert breaks 22 | - generate tests 23 | - re-run tests on change (watch) 24 | - use coverage data (from a full-run) to know which tests to re-run 25 | - command: toggle watch 26 | - command: toggle watch for specific tests or suite 27 | - run with coverage 28 | - show test "status" in the editor (e.g. in emacs' header-line or mode-line) 29 | - run in a different process! 30 | - mutation testing 31 | 32 | it's possible to narrow down which test is probably points to the 33 | right root cause when a lot of tests fails by inspecting the relations 34 | of the functions being called by the tests (i.e. infer the 35 | dependencies between the tests). 36 | 37 | |# 38 | 39 | ;; TODO insert-test-system-definition 40 | 41 | ;; TODO defmethods 42 | ;; TODO support multiple test framework _at the same time_ 43 | 44 | 45 | ;;; Functions for working with test frameworks 46 | 47 | (defvar *known-test-frameworks* 48 | '(1am 49 | cacau 50 | check-it 51 | checkl 52 | cl-naive-test 53 | cl-quickcheck 54 | clite 55 | clue #| used by alive, not in quicklisp https://github.com/nobody-famous/clue |# 56 | clunit 57 | clunit2 58 | fiasco 59 | fiveam 60 | lift 61 | lisp-unit 62 | lisp-unit2 63 | monkeylib-test-framework 64 | parachute 65 | parten-test 66 | ptester 67 | rove 68 | prove #| archived in 2020 |# 69 | rt 70 | should-test 71 | simplet 72 | stefil 73 | test-urils 74 | testiere 75 | try 76 | unit-test 77 | xlunit)) 78 | 79 | ;; protest 80 | 81 | (defun detect-all-test-framework ()) 82 | 83 | 84 | ;;; Commands for working with tests 85 | 86 | (define-command run-test-at-point () 87 | "Run the test at point." 88 | 'not-implemented-yet) 89 | 90 | (define-command run-tests-in-file () 91 | "Run the all the tests in a file." 92 | 'not-implemented-yet) 93 | 94 | (define-command run-tests-in-suite () 95 | "Run the all the tests in a suite." 96 | 'not-implemented-yet) 97 | 98 | (define-command run-tests-in-package () 99 | "Run the all the tests in a package." 100 | 'not-implemented-yet) 101 | 102 | (define-command run-system-tests () 103 | "Run a system's tests using ~(asdf:test-system)~." 104 | 'not-implemented-yet) 105 | 106 | (define-command run-all-tests () 107 | "Run all the tests..." 108 | 'not-implemented-and-not-quite-sure-exactly-what-this-should-do) 109 | 110 | (define-command quickfix-test () 111 | "Tries to fix the test(s) automagically." 112 | 'not-implemented 113 | #| If the assertion is not complete, run the code and try to 114 | generate the missing part (the "expected" value). |#) 115 | 116 | (define-command goto-test () 117 | "Go to a specific test." 118 | 'not-implemented) 119 | 120 | (define-command undefine-test-at-point () 121 | "Remove the test at point" 122 | 'not-implemented) 123 | 124 | (define-command move-to-tests () 125 | "Move the code at point to a test file." 126 | 'not-implemented) 127 | -------------------------------------------------------------------------------- /scratch-files/test-runner.lisp: -------------------------------------------------------------------------------- 1 | ;;;; The test runner is a separate thread that receives request to run test. 2 | ;;;; We use a thread (as opposed to straight up running the tests) in 3 | ;;;; order to debounce and group the requests. 4 | 5 | ;;; TODO Logging (maybe use log4cl) 6 | 7 | (defpackage #:breeze.test-runner 8 | (:documentation "Provides a test-runner (and methods to interact 9 | with it). Alternatively, you _could_ run many different 10 | test-runners.") 11 | ;; TODO Don't USE alexandria and anaphora, perhaps not breeze.worker either 12 | (:use :cl #:alexandria #:anaphora 13 | #:breeze.worker) 14 | (:export #:start-test-runner 15 | #:stop-test-runner 16 | #:ensure-test-runner 17 | #:request-to-run-test 18 | #:request-to-run-test*) 19 | (:import-from #:breeze.test 20 | #:run-all-tests)) 21 | 22 | (in-package #:breeze.test-runner) 23 | 24 | (defclass test-runner (worker) 25 | ((messages :initform ()) 26 | (received-messages-last-iteration-p :initform nil)) 27 | (:documentation "A test runner is a worker that run tests on demand (with some debouncing.")) 28 | 29 | (defvar *test-runner* (make-instance 'test-runner 30 | :interval 0.25 31 | :name "breeze's test runner") 32 | "The main test-runner.") 33 | 34 | (defun process-messages (message-list) 35 | "Take a list of messages (already debounced), deduplicate and process them." 36 | (run-all-tests 37 | (remove-duplicates message-list &key #'car))) 38 | 39 | (defun receive-messages () 40 | (worker-receive-all-messages *test-runner*)) 41 | 42 | (defmethod worker-report ((test-runner test-runner) level control-string &rest format-arguments) 43 | ;; Make the test-worker quiet (do nothing) 44 | ;; (call-next-method) ;; <- prints to standard-output 45 | ) 46 | 47 | (defmethod worker-run ((test-runner test-runner)) 48 | (with-slots (messages received-messages-last-iteration-p) 49 | test-runner 50 | (worker-report test-runner :debug "~%running test runner~%Messages: ~A~%received: ~A" 51 | messages received-messages-last-iteration-p) 52 | (aif (receive-messages) 53 | (setf messages (append messages it) 54 | received-messages-last-iteration-p t) 55 | (setf received-messages-last-iteration-p nil)) 56 | (when (and messages (not received-messages-last-iteration-p)) 57 | (process-messages messages) 58 | (setf received-messages-last-iteration-p nil 59 | messages nil)))) 60 | 61 | (defun start-test-runner () 62 | "Start the test runner" 63 | ;; Empty the message queue before starting. 64 | (receive-messages) 65 | (worker-start *test-runner*)) 66 | 67 | (defun stop-test-runner () 68 | "Stop the test runner" 69 | (worker-stop *test-runner*)) 70 | 71 | (defun ensure-test-runner () 72 | "Start the test runner if it's not already running." 73 | (worker-ensure-alive *test-runner*)) 74 | 75 | (defun request-to-run-test (test) 76 | "Take a test name and send it to the test-runner." 77 | (worker-send *test-runner* (list test (get-universal-time)))) 78 | 79 | (defun request-to-run-test* (test-list) 80 | "Take a list of test name and send it to the test-runner." 81 | (map nil #'request-to-run-test test-list)) 82 | 83 | ;; Bunch of calls used to manually test the test-runner's logic 84 | #| 85 | (worker-start *test-runner*) 86 | (worker-stop *test-runner*) 87 | (worker-alive-p *test-runner*) 88 | 89 | (request-to-run-test 'a) 90 | (request-to-run-test* '(a b c))) 91 | (progn 92 | (request-to-run-test 'a) 93 | (request-to-run-test* '(a b c))) 94 | |# 95 | -------------------------------------------------------------------------------- /scripts/demo/demo-recorder.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script is meant to be run as the main script _inside docker_ 4 | # 5 | # It takes care of starting sbcl, load breeze, start swank, start xvfb 6 | # and emacs. 7 | # 8 | 9 | set -xeuo pipefail 10 | 11 | # TODO test if `git` is available first 12 | # This is for running the script outside a container, which is not supported as well (for the moment) 13 | # # Move to repo's root 14 | # cd "$(git rev-parse --show-toplevel)" 15 | 16 | export DEMO_LISTENER_PORT=40050 17 | export DEMO_OUTPUT_DIR=${DEMO_OUTPUT_DIR:-scripts/demo/output} 18 | 19 | mkdir -p ${DEMO_OUTPUT_DIR} 20 | 21 | listener_ready_file="listener-ready" 22 | director_logs=${DEMO_OUTPUT_DIR}/emacs-director.log 23 | 24 | # Keeping that because I might want to demo something that uses 25 | # quicklisp. 26 | # Example of how to use quicklisp: 27 | # --load "/root/quicklisp/setup.lisp" \ 28 | # --eval "(asdf:load-asd \"/breeze/breeze.asd\")" \ 29 | # --eval "(ql:quickload '(#:breeze #:swank))" \ 30 | 31 | # Starting sbcl in a screen 32 | screen -dm sbcl --noinform \ 33 | --core dependencies.core \ 34 | --eval "(asdf:load-system '#:swank :verbose t :force-not (asdf:already-loaded-systems))" \ 35 | --eval "(asdf:load-asd \"/breeze/breeze.asd\")" \ 36 | --eval "(asdf:load-system '#:breeze :verbose t :force-not (asdf:already-loaded-systems))" \ 37 | --eval "(swank:create-server :dont-close t :port ${DEMO_LISTENER_PORT})" \ 38 | --eval "(with-open-file (_ \"$listener_ready_file\" :if-does-not-exist :create))" 39 | 40 | function wait_for_file() { 41 | while test ! -f $1 42 | do 43 | sleep 1 44 | done 45 | sleep 1 46 | } 47 | 48 | echo "Waiting for swank to startup in the background..." 49 | wait_for_file $listener_ready_file 50 | echo "Swank is up!" 51 | 52 | emacs_args=( 53 | # Don't load any init file 54 | -Q 55 | # Load the emacs-director's loader 56 | -l scripts/emacs-director/util/director-bootstrap.el 57 | # Run in fullscreen 58 | --fullscreen 59 | # Run our demo 60 | -l scripts/demo/demo.el 61 | # Trying to pass extra arguments 62 | base-demo 63 | ) 64 | 65 | function emacs_x11() { 66 | export DISPLAY=:99 67 | 68 | # -s "-screen 0 1280x800x32" 69 | xvfb-run -e ${DEMO_OUTPUT_DIR}/xvfb-errors.log emacs "${emacs_args[@]}" & 70 | ## sleep 2 71 | ## tail -f $director_logs | sed '/END/q' 72 | 73 | # TODO This is Work in progress.. 74 | # I think I should run the whole x11 stuff in screen 75 | # if command x11vnc; then 76 | # TODO vnc 77 | # x11vnc -o ${DEMO_OUTPUT_DIR}/x11vnc.log -display ${DISPLAY} -bg 78 | # -xkb 79 | # -noxrecord -noxfixes -noxdamage 80 | # -repeat -nopw 81 | # -wait 5 82 | # -permitfiletransfer -tightfilexfer 83 | # fi 84 | } 85 | 86 | function emacs_tty() { 87 | # For debugging 88 | emacs -nw "${emacs_args[@]}" 89 | } 90 | 91 | function wait_for_emacs_to_stop() { 92 | # TODO Time out? 93 | while pgrep emacs 94 | do 95 | sleep 1 96 | done 97 | } 98 | 99 | function dump() ( 100 | set +e 101 | echo 102 | find -name '*.log' 103 | echo 104 | find / -name 'breeze*.png' 105 | echo 106 | cat $director_logs 107 | echo 108 | cat ${DEMO_OUTPUT_DIR}/messages.log 109 | ls ${DEMO_OUTPUT_DIR} 110 | ) 111 | 112 | trap "echo SIGINT; dump; sh" SIGINT # ^c 113 | 114 | emacs_x11 115 | # wait_for_file $director_logs 116 | 117 | sleep 1 118 | wait_for_emacs_to_stop 119 | # emacsclient -nw 120 | 121 | ### below, it's all for testing 122 | 123 | dump 124 | 125 | # if the standard input is a terminal, then open a shell, for 126 | # interactive debugging 127 | if [ -t 0 ]; then 128 | bash 129 | fi 130 | -------------------------------------------------------------------------------- /docs/syntax_highlighting.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 8a919b83-89f4-40b8-aeb4-2638c308cddf 3 | :END: 4 | #+title: Syntax highlighting 5 | 6 | Implementing syntax highlighting "in breeze" would: 7 | - improve the situation in emacs 8 | - provide the same "backend" for each editors 9 | - be a very good test for the parser 10 | 11 | * Syntax highlighting in Emacs 12 | 13 | Oftentimes, the syntax highlighting is not exactly right. 14 | 15 | I can't remember a specific example of the syntax highlighting beging 16 | _broken_ (not just inexact) rigth this moment, but it's easy to 17 | imagine a custom reader macro that would cause issues. 18 | 19 | One example, the "cl keywords" (as defined by emacs' syntax 20 | highlighting) are detected as such even if the symbol is actually 21 | shadowed. This is not _always_ want we want. 22 | 23 | It would be nice to have the cl:loop keywords highlighted even when 24 | they're not :keywords. 25 | 26 | The various "literals" are not highlighted at all (like =#\Return=, or 27 | =#x1A=). 28 | 29 | It could be nice to have highlighting specific to a macro... That 30 | would be hard in general, but it might be possible for quite a few 31 | macros... 32 | 33 | It could be nice to highlight the cl:format's control strings. 34 | 35 | ** How syntax highlighting works in emacs 36 | 37 | #+begin_quote 38 | font-lock-mode is the standard way to have Emacs perform syntax 39 | highlighting in the current buffer. It is enabled by default. 40 | #+end_quote 41 | From [[https://www.gnu.org/software/emacs/manual/html_node/efaq/Turning-on-syntax-highlighting.html][How do I turn on syntax highlighting?]] 42 | 43 | See info node: [[info:emacs#Font Lock][emacs#Font Lock]] 44 | 45 | - =font-lock-mode= uses predefined font faces like 46 | =font-lock-string-face=/ 47 | - it has a "just in time" syntax highlighting which is called 48 | =jit-locking= 49 | 50 | Syntax highlighting can be done in different ways: 51 | - regex 52 | - syntax tables 53 | - "Use syntax tree produced by a full-blown parser" ← bingo 54 | - it is called "Parser-based font lock" 55 | 56 | *** Parser-based Font Lock 57 | 58 | See info node: [[info:emacs#Parser-based Font Lock][emacs#Parser-based Font Lock]] 59 | 60 | aannnd disappointment, it only talks about treesitter... 61 | 62 | ** Apparently, I'm not the only one wanting the improve CL font-lock 63 | 64 | https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/ 65 | 66 | *** TODO Explore this blog and add notes in there 67 | 68 | This blog seems to have a lot of posts about using lisp and tweaking 69 | editors (emacs) to make it event better. 70 | 71 | ** DIY fontification ?? 72 | 73 | *** Using syntax table 74 | 75 | [[info:elisp#Syntax Tables][elisp#Syntax Tables]] are used by Font lock mode... 76 | 77 | https://www.emacswiki.org/emacs/EmacsSyntaxTable 78 | 79 | #+begin_quote 80 | When the syntax table is not flexible enough to specify the syntax of 81 | a language, you can override the syntax table for specific character 82 | occurrences in the buffer, by applying a syntax-table text property. 83 | #+end_quote 84 | From [[info:elisp#Syntax Properties][elisp#Syntax Properties]] 85 | 86 | The function =syntax-propertize-function= is used by font-lock mode 87 | during "during syntactic fontification". 88 | 89 | This [[https://stackoverflow.com/a/25251144][StackOverflow answer]] seems to be a good example of how to 90 | customize this... And I _think_ it would be the right place 91 | 92 | Use =setq-local= though.. or =(make-local-variable 93 | 'syntax-propertize-function)= 94 | 95 | Another interesting explanation of =syntax-propertize-function=. 96 | 97 | =lisp-mode-syntax-table= is the name of the variable containing the 98 | syntax table object for the =lisp-mode=. 99 | 100 | *** Using text properties 101 | 102 | I _could_ set the ='face= property manually too... (it overrides the 103 | ='font-lock-face= property). 104 | -------------------------------------------------------------------------------- /tests/xref.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (uiop:define-package #:breeze.test.xref 4 | (:documentation "Tests for breeze.xref.") 5 | (:mix #:breeze.xref #:cl #:alexandria) 6 | (:import-from #:parachute 7 | #:define-test 8 | #:is 9 | #:true 10 | #:false)) 11 | 12 | (in-package #:breeze.test.xref) 13 | 14 | ;; TODO this test is useful, but doesn't scale as-is 15 | #++ 16 | (define-test find-package 17 | (is equal 18 | (find-packages-by-prefix "breeze") 19 | (find-packages-by-prefix "breeze."))) 20 | 21 | (defparameter *symbols* 22 | '(dum:*bound-variable* 23 | dum:*unbound-variable* 24 | dum:a-class 25 | dum:a-function 26 | dum:a-macro 27 | dum:slot 28 | (setf dum:slot))) 29 | 30 | (define-test predicate-dont-signal-any-error 31 | (do-external-symbols (symbol 'breeze.dummy.test) 32 | (mapcar #'(lambda (predicate) 33 | (funcall predicate symbol)) 34 | '(generic-method-p 35 | classp 36 | specialp 37 | macrop 38 | simple-function-p)))) 39 | 40 | (define-test generic-method-p 41 | (false (generic-method-p 'dum:*bound-variable*)) 42 | (false (generic-method-p 'dum:*unbound-variable*)) 43 | (false (generic-method-p 'dum:a-class)) 44 | (false (generic-method-p 'dum:a-function)) 45 | (false (generic-method-p 'dum:a-macro)) 46 | (true (generic-method-p 'dum:slot)) 47 | (true (generic-method-p '(setf dum:slot)))) 48 | 49 | (define-test specialp 50 | (true (specialp 'dum:*bound-variable*)) 51 | (true (specialp 'dum:*unbound-variable*)) 52 | (false (specialp 'dum:a-class)) 53 | (false (specialp 'dum:a-function)) 54 | (false (specialp 'dum:a-macro)) 55 | (false (specialp 'dum:slot)) 56 | (false (specialp '(setf dum:slot)))) 57 | 58 | (define-test macrop 59 | (false (macrop 'dum:*bound-variable*)) 60 | (false (macrop 'dum:*unbound-variable*)) 61 | (false (macrop 'dum:a-class)) 62 | (false (macrop 'dum:a-function)) 63 | (true (macrop 'dum:a-macro)) 64 | (false (macrop 'dum:slot)) 65 | (false (macrop '(setf dum:slot)))) 66 | 67 | (define-test simple-function-p 68 | (false (simple-function-p 'dum:*bound-variable*)) 69 | (false (simple-function-p 'dum:*unbound-variable*)) 70 | (false (simple-function-p 'dum:a-class)) 71 | (true (simple-function-p 'dum:a-function)) 72 | (false (simple-function-p 'dum:a-macro)) 73 | (false (simple-function-p 'dum:slot)) 74 | (false (simple-function-p '(setf dum:slot)))) 75 | 76 | 77 | ;; I used this to generate the tests for classp 78 | #+nil 79 | (let* ((fn 'classp) 80 | (test-cases 81 | (with-output-to-string (*standard-output*) 82 | (loop :for symbol :being :the :external-symbol :of 'breeze.dummy.test 83 | :for pass = (funcall fn symbol) 84 | :unless (search "undocumented" (string-downcase (symbol-name symbol))) 85 | :do 86 | (format t "~&(is ") 87 | (unless pass (format t "(not ")) 88 | (format t "(~a 'dum:~a)" fn symbol) 89 | (unless pass (format t ")")) 90 | (format t ")"))))) 91 | (format t "(define-test ~(~a~%~a~))" fn 92 | (breeze.string:indent-string 2 test-cases))) 93 | 94 | (define-test classp 95 | (false (classp 'dum:*bound-variable*)) 96 | (false (classp 'dum:slot)) 97 | (true (classp 'dum:a-class)) 98 | (false (classp 'dum:a-generic-function)) 99 | (false (classp 'dum:a-macro)) 100 | (false (classp 'dum:another-generic-function)) 101 | (false (classp 'dum:*unbound-variable*)) 102 | (false (classp 'dum:a-function))) 103 | 104 | (define-test externalp 105 | (true (externalp 'cl:null)) 106 | (null (externalp 'this-unexported-symbol)) 107 | (false (externalp '#:uninterned-symbol)) 108 | (false (externalp (gensym)))) 109 | -------------------------------------------------------------------------------- /tests/workspace.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.test.workspace 2 | (:documentation "Test package for #:breeze.workspace") 3 | (:use #:cl #:breeze.workspace) 4 | (:import-from #:breeze.indirection 5 | #:with-simple-indirections) 6 | (:import-from #:parachute 7 | #:define-test 8 | #:define-test+run 9 | #:is 10 | #:true 11 | #:false 12 | #:of-type 13 | #:finish) 14 | (:export #:*breeze-workspace*)) 15 | 16 | (in-package #:breeze.test.workspace) 17 | 18 | 19 | 20 | (define-test infer-project-name 21 | (false 22 | (with-simple-indirections 23 | ((breeze.utils:find-version-control-root)) 24 | (infer-project-name "some path")) 25 | "infer-project-name should return nil if the version control root directory was not found") 26 | (is string= "foobar" 27 | (with-simple-indirections 28 | ((breeze.utils:find-version-control-root 29 | #p"/home/nobody/projects/foobar/")) 30 | (infer-project-name "some path")) 31 | "infer-project-name should return the name of the version control root directory when it is found")) 32 | 33 | (defun goto-all-positions ($node) 34 | (loop 35 | :with length = (length (breeze.parser:source $node)) 36 | :for i :below length 37 | :do (breeze.parser:goto-position $node i))) 38 | 39 | (defparameter *breeze-workspace* nil) 40 | 41 | ;; this tests add-to-workspace 42 | (define-test+run *breeze-workspace* 43 | (let ((*workspace* (make-workspace))) 44 | (add-files-to-workspace 45 | (breeze.asdf:find-all-related-files 'breeze)) 46 | (setf *breeze-workspace* *workspace*))) 47 | 48 | (define-test+run add-to-workspace 49 | :depends-on (*breeze-workspace*) 50 | (finish 51 | (map-workpace-buffers 52 | (lambda (buffer 53 | &aux ($node (breeze.parser:node-iterator buffer))) 54 | (finish 55 | (progn ;; time 56 | (goto-all-positions $node)) 57 | "Should be able to \"goto\" every positions in ~s" (name buffer)))))) 58 | 59 | (defun suffixp (got expected-suffix) 60 | (alexandria:ends-with-subseq expected-suffix got)) 61 | 62 | (define-test+run locate-package-definition 63 | :depends-on (*breeze-workspace*) 64 | (let ((*workspace* *breeze-workspace*)) 65 | (let ((location (finish (locate-package-definition "breeze.string")))) 66 | (true location 67 | "Should have been able to find the breeze.string's package definition.") 68 | (true (getf location :buffer) 69 | "Should have returned a plist that contains the key :buffer.") 70 | (true (getf location :node-iterator) 71 | "Should have returned a plist that contains the key :node-iterator.") 72 | (destructuring-bind (&key buffer node-iterator) location 73 | (when (and buffer node-iterator) 74 | (is suffixp "src/string-utils.lisp" (name buffer)) 75 | (is string= #1="(defpackage #:breeze.string 76 | (:documentation \"String manipulation utilities\")" 77 | (subseq (breeze.parser:node-string node-iterator) 78 | 0 (length #1#)))))) 79 | (let ((location (finish (locate-package-definition "breeze.parser")))) 80 | (true location 81 | "Should have been able to find the breeze.parser's package definition.") 82 | (true (getf location :buffer) 83 | "Should have returned a plist that contains the key :buffer.") 84 | (true (getf location :node-iterator) 85 | "Should have returned a plist that contains the key :node-iterator.") 86 | (destructuring-bind (&key buffer node-iterator) location 87 | (when (and buffer node-iterator) 88 | (is suffixp "src/parser/parser.lisp" (name buffer)) 89 | (is string= #2="(uiop:define-package #:breeze.parser 90 | (:documentation \"A fast, lossless, robust and " 91 | (subseq (breeze.parser:node-string node-iterator) 92 | 0 (length #2#)))))))) 93 | -------------------------------------------------------------------------------- /docs/brightlight-green.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Nunito");html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,textarea,input,select,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{font-family:"Nunito",sans-serif;color:#000}body,html{padding:0;margin:0;overflow-x:hidden;background-color:#fff}nav{font-family:"Nunito",sans-serif;background-color:#212121;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex}nav header{padding:8px;display:inline}nav header a{text-decoration:none;color:#fff}nav header a:hover{color:#00c853}nav header+input:checked+div{display:block}nav div{display:none;margin-left:auto}nav div ul{list-style:none;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px}nav div ul li a{display:inline-block;padding:8px;color:#fff;text-decoration:none}nav div ul li:hover{background-color:#00c853}nav div ul li ul{right:0;position:relative;background:pink}@media (max-width: 630px){nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}nav header{text-align:center}nav div{margin:auto}nav div ul{padding:0}nav div ul li{border-top-left-radius:5px;border-top-right-radius:5px}nav div ul li[active],nav div ul li.active{border:1px solid #00c853;border-bottom:none}blockquote{border-left:4px solid #00c853;padding:8px 5px;margin:0}blockquote p{font-size:0.4rem}}@media (min-width: 630px){nav div ul li.active,nav div ul li[active]{border:1px solid #00c853;border-bottom:none;border-top:none}nav header label{display:none}}section[container]{max-width:38em;margin:auto;padding:5px}h1{font-size:2.35em}h2{font-size:2em}h3{font-size:1.75em}h4{font-size:1.5em}h5{font-size:1.25em}h6{font-size:1em}a{color:#00c853}a:hover{color:#212121}mark{background-color:#00c853}code{font-family:monospace;background-color:#bdbdbd;padding-left:5px;padding-right:5px}blockquote{border-left:4px solid #00c853;padding:8px 10px;width:100%}blockquote p{font-style:italic;font-size:1.1rem}blockquote footer::before{content:"\2014 \00A0"}blockquote footer cite{font-style:italic;color:#bdbdbd}pre{background:#eee;overflow-x:auto;text-align:left;padding:5px}pre code{display:block;padding:0 10px;background:transparent}table{display:table;padding:5px;border-collapse:collapse}table thead,table tbody{text-align:left}table tr th,table tr td{padding:5px 10px;border-bottom:1px solid #00c853}div[overflow]{overflow-x:auto;max-width:100vw}div[overflow] ::-webkit-scrollbar{height:0}img{max-width:100%;border-radius:5px}form div{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 5px}form div p{margin:0px}form input,form select{font-size:1rem;padding:5px;border:1px solid #bdbdbd;color:#212121}form input:active,form input:focus,form select:active,form select:focus{outline-color:#00c853}form input[type="submit"]{padding:10px;background-color:#00c853;color:#000;border-radius:5px;border:none;cursor:pointer}form input[type="submit"]:active,form input[type="submit"]:focus{outline:none}form input[type="submit"]:active{background-color:#212121;color:#00c853}form input[type="submit"]:disabled{background:#bdbdbd;cursor:not-allowed}form input[type="submit"][secondary]{background-color:#212121;color:#00c853}form input[type="submit"][secondary]:active{background-color:#00c853;color:initial}textarea{color:#212121;width:-webkit-fill-available;font-size:1rem;padding:5px}textarea:active,textarea:focus{outline-color:#00c853}button{padding:10px;background-color:#00c853;color:#000;border-radius:5px;border:none;cursor:pointer}button:active,button:focus{outline:none}button:active{background-color:#212121;color:#00c853}button:disabled{background:#bdbdbd;cursor:not-allowed}button[secondary]{background-color:#212121;color:#00c853}button[secondary]:active{background-color:#00c853;color:initial}body>footer{background-color:#212121;position:relative;bottom:0;width:100%;padding:5px;color:#fff} 2 | /*# sourceMappingURL=brightlight-green.css.map */ -------------------------------------------------------------------------------- /tests/report.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:breeze.test.report 2 | (:documentation "Tests for the package breeze.report") 3 | (:use #:cl #:breeze.report) 4 | ;; importing non-exported symbols, for testing 5 | (:import-from #:breeze.report 6 | #:render-files 7 | #:pages) 8 | (:import-from #:parachute 9 | #:define-test 10 | #:define-test+run 11 | #:is 12 | #:true 13 | #:false 14 | #:of-type 15 | #:finish 16 | #:fail)) 17 | 18 | (in-package #:breeze.test.report) 19 | 20 | (define-test+run report 21 | (fail (make-instance 'report)) 22 | (finish (make-instance 'report :output-dir "out/"))) 23 | 24 | (define-test+run slug 25 | :dependencies (report) 26 | (is string= "nil" (slug nil nil)) 27 | (is string= "" (slug nil "")) 28 | (is string= "" (slug nil " ")) 29 | (is string= "" (slug nil " ")) 30 | (is string= "" (slug nil (string #\newline))) 31 | (is string= "233csmth3e" (slug nil "#")) 32 | (is string= "a--path--to--some--file.txt" (slug nil "a/path/to/some/file.txt"))) 33 | 34 | (define-test+run url-to 35 | :dependencies (slug) 36 | (is string= "nil" (url-to nil nil nil)) 37 | (is string= "tests--report.lisp" 38 | (url-to nil "tests/report.lisp" nil)) 39 | (is string= "foo-tests--report.lisp" 40 | (url-to nil "tests/report.lisp" :foo)) 41 | (is string= "listing-tests--report.lisp.html" 42 | (url-to nil "tests/report.lisp" :listing))) 43 | 44 | (define-test+run pathname-to 45 | :dependencies (slug) 46 | (is equalp #P"out/listing-tests--report.lisp.html" 47 | (pathname-to (make-instance 'report :output-dir "out/") 48 | "tests/report.lisp" :listing))) 49 | 50 | (define-test+run link-to-id 51 | (is string= "42" (link-to-id nil "42" "life"))) 52 | 53 | (define-test+run link-to-file 54 | :dependencies (pathname-to) 55 | (is string= 56 | "asdf" 57 | (link-to-file 58 | (make-instance 'report :output-dir "out/") "asdf"))) 59 | 60 | (define-test+run link-to-page 61 | :dependencies (pathname-to) 62 | (is string= 63 | "asdf — untitled page 42" 64 | (link-to-page 65 | (make-instance 'report :output-dir "out/") "asdf" 42)) 66 | (is string= 67 | "Not untitled!" 68 | (link-to-page 69 | (make-instance 'report :output-dir "out/") "asdf" 42 70 | "Not untitled!"))) 71 | 72 | (define-test+run paragraphs 73 | (is equalp `("asd" ,(format nil "qwe~%ert") "jkl") 74 | (paragraphs 75 | (format nil "asd~5%qwe~%ert~2%jkl")))) 76 | 77 | (define-test+run remove-leading-semicolons 78 | (is string= "" (remove-leading-semicolons "; ; ; "))) 79 | 80 | (define-test+run escape-html 81 | (is string= "
" (escape-html "
")) 82 | (is string= "<a>" (escape-html "")) 83 | (is string= "" (escape-html "")) 84 | (is string= "=> (#<ASDF/SYSTEM:SYSTEM \"breeze/test\"> #<ASDF/SYSTEM:SYSTEM \"breeze/config\">)" 85 | (escape-html "=> (# #)"))) 86 | 87 | 88 | ;;; TODO I want to generate example pages, especially for commands 89 | 90 | ;; TODO extract string-utils's around function's tests 91 | ;; TODO eval-node would be useful! 92 | 93 | ;; Here I render a buffer with syntax errors into an html file 94 | ;; TODO extract function "render-buffer" out of "render-lisp-flle" 95 | #++ 96 | (let* ((root (merge-pathnames 97 | "docs/" 98 | (asdf:system-source-directory 'breeze))) 99 | (content "( 100 | #| 101 | #r") 102 | (filename "example.lisp") 103 | (state (breeze.parser:parse content)) 104 | (pages (pages state))) 105 | (;; with-output-to-string (out) 106 | ;; alexandria:with-output-to-file (out (merge-pathnames "example.lisp.html" root)) 107 | progn 108 | (render-files 109 | (make-instance 'report :output-dir root) 110 | (list (list filename state pages)) 111 | (merge-pathnames "example.lisp.html" root)))) 112 | --------------------------------------------------------------------------------