Writing a best-efgort portable code walker in Common Lisp Michael - - PowerPoint PPT Presentation

writing a best efgort portable code walker in common lisp
SMART_READER_LITE
LIVE PREVIEW

Writing a best-efgort portable code walker in Common Lisp Michael - - PowerPoint PPT Presentation

. . . . . . . . . . . . . . . Writing a best-efgort portable code walker in Common Lisp Michael Raskin, raskin@mccme.ru Aarhus University LaBRI, Universit de Bordeaux April 2017 Michael Raskin, raskin@mccme.ru (LaBRI)


slide-1
SLIDE 1

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Writing a best-efgort portable code walker in Common Lisp

Michael Raskin, raskin@mccme.ru

Aarhus University → LaBRI, Université de Bordeaux

April 2017

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 1 / 24

slide-2
SLIDE 2

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Code walking: what and why

Code walker · a tool for code analysis and transformation · enumerates all the subforms in the code Why? · Code is data is code (so why not) · Metaprogramming: Write programs that write programs ·· Macros ·· Write programs that rewrite programs!

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 2 / 24

slide-3
SLIDE 3

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Code walking: what and why

Code walker · a tool for code analysis and transformation · enumerates all the subforms in the code Why? · Code is data is code (so why not) · Metaprogramming: Write programs that write programs ·· Macros ·· Write programs that rewrite programs!

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 2 / 24

slide-4
SLIDE 4

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Code walking: what and why

Code walker · a tool for code analysis and transformation · enumerates all the subforms in the code Why? · Code is data is code (so why not) · Metaprogramming: Write programs that write programs ·· Macros ·· Write programs that rewrite programs!

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 2 / 24

slide-5
SLIDE 5

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Is code walking used in Common Lisp?

· iterate What else? · CL-Web · CL-Cont ·· Weblocks · hu.dwim.walker ·· local-variable-debug-wrapper · macroexpand-dammit ·· sexml ·· fn ·· temporal-functions · trivial-macroexpand-all · Some implementations include code walking libraries · Papers with a placeholder for some implementation-specifjc function This may be close to a complete list…

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 3 / 24

slide-6
SLIDE 6

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Is code walking used in Common Lisp?

· iterate What else? · CL-Web · CL-Cont ·· Weblocks · hu.dwim.walker ·· local-variable-debug-wrapper · macroexpand-dammit ·· sexml ·· fn ·· temporal-functions · trivial-macroexpand-all · Some implementations include code walking libraries · Papers with a placeholder for some implementation-specifjc function This may be close to a complete list…

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 3 / 24

slide-7
SLIDE 7

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

A use of code walking: fn

Why use a code walker: a small example (fn* (+ _ _)) → (lambda (_) (+ _ _)) (fn* (+ _ _1)) → (lambda (_ _1) (+ _ _1)) (fn* (subseq _@ 0 2)) → (lambda (&rest _@) (subseq _@ 0 2)) λ(+ _ _1) → (lambda (_ _1) (+ _ _1)) from README of public domain fn library by Chris Bagley (Baggers) Code walking is used to fjnd argument names (not quoted symbols or function names or local variables)

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 4 / 24

slide-8
SLIDE 8

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Are there reusable code walking libraries?

Implementation-supplied libraries · Implement exactly what they promise — correctly · API difgers hu.dwim.walker · Reader conditionals · Bit rot in the code for some of the implementations · Removes macrolet from code macroexpand-dammit · Portable · Correctness problems ·· Some of them avoidable · Removes macrolet from code

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 5 / 24

slide-9
SLIDE 9

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Is a portable code walker possible in Common Lisp?

Functionality we can specify: a macroexpand-all function Like macroexpand, but also expand subforms Should take a lexical environment object as the second parameter (for local macros)

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 6 / 24

slide-10
SLIDE 10

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Is a portable macroexpand-all function possible in Common Lisp as defjned by ANSI?

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 7 / 24

slide-11
SLIDE 11

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Is a portable macroexpand-all function possible in Common Lisp as defjned by ANSI?

No

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 7 / 24

slide-12
SLIDE 12

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable macroexpand-all function is impossible: environments

Macro expansion functions have access to lexical environment Can use environment to call macroexpand-1 on arbitrary forms Very powerful feature — even more than it seems

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 8 / 24

slide-13
SLIDE 13

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Tricks with environment

(defmacro depth-limit (max &body body &environment env) (let* ((depth-value (macroexpand-1 (quote (depth-counter)) env)) (depth (if (numberp depth-value) depth-value 0))) (if (> depth max) (progn (format *error-output* "Too deep.~%") nil) `(macrolet ((depth-counter () ,(1+ depth))) ,@body)))) (depth-limit 0 (list (depth-limit 1 :test))) (depth-limit 0 (list (depth-limit 0 :test))) No code-walking!

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 9 / 24

slide-14
SLIDE 14

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

(macrolet ((with-gensym ((x) &body body) `(macrolet ((,x () '',(gensym))) ,@body))) (with-gensym (f1) (with-gensym (f2) (defmacro set-x1 (value &body body) `(macrolet ((,(f1) () ,value)) ,@body)) (defmacro set-x2 (value &body body) `(macrolet ((,(f2) () ,value)) ,@body)) (defmacro read-x1-x2 (&environment env) `(list ',(macroexpand-1 `(,(f1)) env) ',(macroexpand-1 `(,(f2)) env)))))) (defmacro expand-via-function (form &environment e) `',(macroexpand-all (quote ,form) ,e)) (set-x1 1 (set-x2 2 (expand-via-function (set-x2 3 (read-x1-x2)))))

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 10 / 24

slide-15
SLIDE 15

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable correct environment handling is impossible

macroexpand-all doesn’t see the names of temporary macros Lexical environment: pass it as is or build a new one from scratch You cannot create an entry with the name you do not know

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 11 / 24

slide-16
SLIDE 16

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable correct environment handling: ANSI CL and «Common Lisp: the Language» (2nd edition)

«Common Lisp: the Language» has functions to inspect and modify lexical environment objects — enough for code walkers What the non-portable code walkers actually do: expand in the given environment, add new entries as needed when descending into special forms An alternative option: inspect the initial lexical environment, build new lexical environments, put the entries extracted from the original environment there In ANSI Common Lisp standard the lexical environment objects are almost completely opaque

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 12 / 24

slide-17
SLIDE 17

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable correct environment handling: ANSI CL and «Common Lisp: the Language» (2nd edition)

«Common Lisp: the Language» has functions to inspect and modify lexical environment objects — enough for code walkers What the non-portable code walkers actually do: expand in the given environment, add new entries as needed when descending into special forms An alternative option: inspect the initial lexical environment, build new lexical environments, put the entries extracted from the original environment there In ANSI Common Lisp standard the lexical environment objects are almost completely opaque

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 12 / 24

slide-18
SLIDE 18

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable correct environment handling: ANSI CL and «Common Lisp: the Language» (2nd edition)

«Common Lisp: the Language» has functions to inspect and modify lexical environment objects — enough for code walkers What the non-portable code walkers actually do: expand in the given environment, add new entries as needed when descending into special forms An alternative option: inspect the initial lexical environment, build new lexical environments, put the entries extracted from the original environment there In ANSI Common Lisp standard the lexical environment objects are almost completely opaque

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 12 / 24

slide-19
SLIDE 19

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Portable correct environment handling: ANSI CL and «Common Lisp: the Language» (2nd edition)

«Common Lisp: the Language» has functions to inspect and modify lexical environment objects — enough for code walkers What the non-portable code walkers actually do: expand in the given environment, add new entries as needed when descending into special forms An alternative option: inspect the initial lexical environment, build new lexical environments, put the entries extracted from the original environment there In ANSI Common Lisp standard the lexical environment objects are almost completely opaque

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 12 / 24

slide-20
SLIDE 20

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

More troubles: expanding standard macros

Let’s expand the defun macro… (defun f (x) x) (progn (eval-when (:compile-toplevel) (sb-c:%compiler-defun 'f nil t)) (sb-impl::%defun 'f (function (sb-int:named-lambda f(x) (block f x))) (sb-c:source-location))) Using SBCL as an example — most implementations do that Special operator function as described in the standard can’t handle this; portable walker needs to deal with difgerent named-lambda symbol names Unclear if the standard intended to allow this

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 13 / 24

slide-21
SLIDE 21

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

More troubles: expanding standard macros

Let’s expand the defun macro… (defun f (x) x) (progn (eval-when (:compile-toplevel) (sb-c:%compiler-defun 'f nil t)) (sb-impl::%defun 'f (function (sb-int:named-lambda f(x) (block f x))) (sb-c:source-location))) Using SBCL as an example — most implementations do that Special operator function as described in the standard can’t handle this; portable walker needs to deal with difgerent named-lambda symbol names Unclear if the standard intended to allow this

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 13 / 24

slide-22
SLIDE 22

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

More troubles: expanding standard macros

Let’s expand the defun macro… (defun f (x) x) (progn (eval-when (:compile-toplevel) (sb-c:%compiler-defun 'f nil t)) (sb-impl::%defun 'f (function (sb-int:named-lambda f(x) (block f x))) (sb-c:source-location))) Using SBCL as an example — most implementations do that Special operator function as described in the standard can’t handle this; portable walker needs to deal with difgerent named-lambda symbol names Unclear if the standard intended to allow this

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 13 / 24

slide-23
SLIDE 23

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

More troubles: expanding standard macros

Let’s expand the defun macro… (defun f (x) x) (progn (eval-when (:compile-toplevel) (sb-c:%compiler-defun 'f nil t)) (sb-impl::%defun 'f (function (sb-int:named-lambda f(x) (block f x))) (sb-c:source-location))) Using SBCL as an example — most implementations do that Special operator function as described in the standard can’t handle this; portable walker needs to deal with difgerent named-lambda symbol names Unclear if the standard intended to allow this

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 13 / 24

slide-24
SLIDE 24

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

More troubles: expanding standard macros

Let’s expand the defun macro… (defun f (x) x) (progn (eval-when (:compile-toplevel) (sb-c:%compiler-defun 'f nil t)) (sb-impl::%defun 'f (function (sb-int:named-lambda f(x) (block f x))) (sb-c:source-location))) Using SBCL as an example — most implementations do that Special operator function as described in the standard can’t handle this; portable walker needs to deal with difgerent named-lambda symbol names Unclear if the standard intended to allow this

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 13 / 24

slide-25
SLIDE 25

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Agnostic Lizard

Portable code walking is almost possible Nobody1 writes such code with expansions · Apply heuristics to decide what environment to pass defun (and defmethod) can be hardcoded · Not a complete solution — user could expand defun and use the result ·· Apply heuristics to guess what style of function extension is used Agnostic Lizard: A code walker No reader conditionals Works fjne unless a combination of bad events happens

1I did — for this talk Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 14 / 24

slide-26
SLIDE 26

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Agnostic Lizard

Enumerates forms and calls callbacks: :on-every-form-pre :on-macroexpanded-form :on-special-form-pre :on-function-form-pre :on-special-form :on-function-form :on-every-atom :on-every-form gitlab.common-lisp.net/ mraskin/agnostic-lizard Callbacks can replace the form Accepts hints about the names, the hints are checked In QuickLisp; also on GitLab.Common-Lisp.net

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 15 / 24

slide-27
SLIDE 27

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Agnostic Lizard anatomy

Three main classes: metaenv, walker-metaenv, macro-walker-metaenv metaenv: basic walking context · Used to defjne metaenv-macroexpand-all walker-metaenv: the same, plus callbacks · Code walking is implemented as macroexpand-all with callbacks and an option to replace forms in the process macro-walker-metaenv: the same, plus support for recursive macro invocations instead of recursive expansion calls · Environment handling fully correct · Some limitations on functionality

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 16 / 24

slide-28
SLIDE 28

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Agnostic Lizard — possible future

Would be interesting to try applying to random code in QuickLisp · Not sure how to check correctness Callback interface · I did use it for some call tracing ·· Had to expand it in the process… · Feature requests are treated with gratitude as advice

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 17 / 24

slide-29
SLIDE 29

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Impact of environment-related extensions: summary

Let’s hope there are no new and creative defun expansion… macroexpand-all and with-augmented-environment and more generic code-walking · Can be used to implement each other · Not much of a performance penalty Environment inspection · Enough to implement macroexpand-all etc. · Transformation uses eval — may be costly · More useful for other debugging tasks

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 18 / 24

slide-30
SLIDE 30

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Using macroexpand-all for environment modifjcation

(defmacro with-current-environment (f &environment env) (funcall f env)) (macroexpand-all `(let ((new-x nil)) (macrolet ((new-f (x) `(1+ ,x))) (with-current-environment ,(lambda (e) …)))) env)

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 19 / 24

slide-31
SLIDE 31

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Constructing an environment given entries

(defmacro eval-with-current-environment ((var) &body code &environment env) `',(funcall (eval `(lambda (,var) ,@code)) env)) (defun with-metaenv-built-env (obj var code) (eval (metaenv-wrap-form

  • bj

`(eval-with-current-environment (,var) ,@code))))

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 20 / 24

slide-32
SLIDE 32

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Analyzing name roles in an environment

An operator in an environment can have: · A visible global macro function · A local macro function (possibly shadowing a global one) · A local function shadowing a global macro defjnition · None of the above — no defjnition, or a function (local or global) ·· Doesn’t matter which Variables and symbol macros are similar but simpler

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 21 / 24

slide-33
SLIDE 33

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Could we please agree on: a common name and package name for named-lambda (portable alternative using labels is in alexandria, a compiler macro could expand to the current expansion, but failing that…) a common package name for macroexpand-all · environment parameter handling can be checked a common name and package for environment-inspection functionality maybe just a common package name for «Common Lisp: the Language» (2nd ed.) functionality not in the standard · most implementations provide most of the functionality already, but package names difger I don’t ask to provide new functionality — just an alias for what exists

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 22 / 24

slide-34
SLIDE 34

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Could we please agree on: a common name and package name for named-lambda (portable alternative using labels is in alexandria, a compiler macro could expand to the current expansion, but failing that…) a common package name for macroexpand-all · environment parameter handling can be checked a common name and package for environment-inspection functionality maybe just a common package name for «Common Lisp: the Language» (2nd ed.) functionality not in the standard · most implementations provide most of the functionality already, but package names difger I don’t ask to provide new functionality — just an alias for what exists

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 22 / 24

slide-35
SLIDE 35

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Could we please agree on: a common name and package name for named-lambda (portable alternative using labels is in alexandria, a compiler macro could expand to the current expansion, but failing that…) a common package name for macroexpand-all · environment parameter handling can be checked a common name and package for environment-inspection functionality maybe just a common package name for «Common Lisp: the Language» (2nd ed.) functionality not in the standard · most implementations provide most of the functionality already, but package names difger I don’t ask to provide new functionality — just an alias for what exists

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 22 / 24

slide-36
SLIDE 36

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Could we please agree on: a common name and package name for named-lambda (portable alternative using labels is in alexandria, a compiler macro could expand to the current expansion, but failing that…) a common package name for macroexpand-all · environment parameter handling can be checked a common name and package for environment-inspection functionality maybe just a common package name for «Common Lisp: the Language» (2nd ed.) functionality not in the standard · most implementations provide most of the functionality already, but package names difger I don’t ask to provide new functionality — just an alias for what exists

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 22 / 24

slide-37
SLIDE 37

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Could we please agree on: a common name and package name for named-lambda (portable alternative using labels is in alexandria, a compiler macro could expand to the current expansion, but failing that…) a common package name for macroexpand-all · environment parameter handling can be checked a common name and package for environment-inspection functionality maybe just a common package name for «Common Lisp: the Language» (2nd ed.) functionality not in the standard · most implementations provide most of the functionality already, but package names difger I don’t ask to provide new functionality — just an alias for what exists

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 22 / 24

slide-38
SLIDE 38

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Pleas

Just in case: common-lisp-extensions:named-lambda common-lisp-extensions:nfunction common-lisp-extensions:macroexpand-all common-lisp-extensions:list-environment-names common-lisp-extensions:with-augmented-environment common-lisp-extensions:with-parent-environment cltl2:parse-macro cltl2:function-information cltl2:variable-information cltl2:declaration-information

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 23 / 24

slide-39
SLIDE 39

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Thanks for the attention Questions?

Michael Raskin, raskin@mccme.ru (LaBRI) Portable Common Lisp code walking April 2017 24 / 24