How to define symbols that will work like ( and ) by symbol macro?
Asked Answered
C

3

6

I am trying define symbols a and b in following way

a + 1 1 b
2

I am trying to do this by using define-symbol-macro

(define-symbol-macro a '( )
(define-symbol-macro b ') )

but this way is not working.

Creationism answered 11/10, 2013 at 13:22 Comment(5)
May be I do not understand symbol-macros at all. (define-symbol-macro a ( ) do not work to.Creationism
( and ) are not symbols. But: why would you want to do that??? What do you want to achieve???Heterosis
Do not fix on 'a' and 'b'. The main reasons are 1) I want to get into macros (and reader-macros, etc.) 2) Long therm conversation with my Python friend about "Python is like Lisp. But insted of stic with parentheses you are stick with indentation." "with lisp you are not stick with parentheses you can create new language without them or use another symbols that suit you better"Creationism
3) Becouse I can. I was told "you can do wathever you like in lisp" and I am looking for some insane things that can not be done in most of languages. I want to see that insane power of lisp and I want to understand it more. Best way I can find is to try do insane things. (I do not use it on "real life" now. I just want to see how it works.)Creationism
OK I love this attitude, and you're actually right, this is Common Lisp Dammit! Lets make this work, see my new answer :)Geddes
M
25

What Lisp does with source code

Common Lisp is an incredibly flexible language, in part because its source code can be easily represented using the same data structures that are used in the language. The most common form of macro expansion transforms the these structures into other structures. These are the kind of macros that you can define with define-symbol-macro, define-compiler-macro, defmacro, and macrolet. Before any of those kind of macroexpansions can be performed, however, the system first needs to read the source from an input stream (typically a file, or an interactive prompt). That's the reader's responsibility. The reader also is capable of executing some special actions when it encounters certain characters, such ( and '. What you're trying to do probably needs to be happening down at the reader level, if you want to have, e.g., (read-from-string "a + 1 1 b") return the list (+ 1 1), which is what you want if you want (eval (read-from-string "a + 1 1 b")) to return 2. That said, you could also define a special custom language (like loop does) where a and b are treated specially.

Use set-macro-character, not define-symbol-macro

This isn't something that you would do using symbol-macros, but rather with macro characters. You can set macro characters using the aptly named set-macro-character. For instance, in the following, I set the macro character for % to be a function that reads a list, using read-delimited-list that should be terminated by ^. (Using the characters a and b here will prove very difficult, because you won't be able to write things like (set-macro-character ...) afterwards; it would be like writing (set-m(cro-ch(r(cter ...), which is not good.)

CL-USER> (set-macro-character #\% (lambda (stream ignore)
                                    (declare (ignore ignore))
                                    (read-delimited-list #\^ stream)))
T
CL-USER> % + 1 1 ^
2

The related set-syntax-from-char

There's a related function that almost does what you want here, set-syntax-from-char. You can use it to make one character behave like another. For instance, you can make % behave like (

CL-USER> (set-syntax-from-char #\% #\()
T
CL-USER> % + 1 1 )
2

However, since the macro character associated with ( isn't looking for a character that has the same syntax as ), but an actual ) character, you can't simply replace ) with ^ in the same way:

CL-USER> (set-syntax-from-char #\^ #\))
T
CL-USER> % + 1 1 ^
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "unmatched close parenthesis" {1002C66031}>.

set-syntax-from-char is more useful when there's an existing character that, by itself does something that you want to imitate. For instance, if you wanted to make ! an additional quotation character:

CL-USER> (set-syntax-from-char #\! #\')
T
CL-USER> (list !a !(1 2 3))
(A (1 2 3))

or make % be a comment character, like it is in LaTeX:

CL-USER> (set-syntax-from-char #\% #\;)
T
CL-USER> (list 1 2 % 3 4
               5 6)
(1 2 5 6)

But consider why you're doing this at all…

Now, even though you can do all of this, it seems like something that would be utterly surprising to anyone who ran into it. (Perhaps you're entering an obfuscated coding competition? ;)) For the reasons shown above, doing this with commonly used characters such as a and b will also make it very difficult to write any more source code. It's probably a better bet to define an entirely new readtable that does what you want, or even write a new parser. even though (Common) Lisp lets you redefine the language, there are still things that it probably makes sense to leave alone.

Millimeter answered 11/10, 2013 at 15:2 Comment(1)
Thank you very much. You just give me a moment of clarity.Creationism
G
3

A symbol-macro is a symbol that stands for another form. Seems like you want to look at reader macros.

I would second Rainer's comment though, what are you trying to make?

Geddes answered 11/10, 2013 at 13:33 Comment(2)
@Joshua Taylor's answer is much better than mine, give it a good read!Geddes
Actually, within a single form this is possible, checkout my new answerGeddes
G
3

Ok so I love your comment on the reason for this and now I know this is for 'Just because it's lisp' then I am totally on board!

Ok so you are right about lisp being great to use to make new languages because we only have to 'compile' to valid lisp code and it will run. So while we cant use the normal compiler to do the transformation of the symbols 'a and 'b to brackets we can write this ourselves.

Ok so lets get started!

(defun symbol-name-equal (a b)
  (and (symbolp a) (symbolp b) (equal (symbol-name a) (symbol-name b))))

(defun find-matching-weird (start-pos open-symbol close-symbol code)
  (unless (symbol-name-equal open-symbol (nth start-pos code))
    (error "start-pos does not point to a weird open-symbol"))
  (let ((nest-index 0))
    (loop :for item :in (nthcdr start-pos code) 
       :for i :from start-pos :do
       (cond ((symbol-name-equal item open-symbol) (incf nest-index 1))
             ((symbol-name-equal item close-symbol) (incf nest-index -1)))
       (when (eql nest-index 0)
         (return i))
       :finally (return nil))))

(defun weird-forms (open-symbol close-symbol body)
  (cond ((null body) nil)
        ((listp body) 
         (let ((open-pos (position open-symbol body :test #'symbol-name-equal)))
           (if open-pos
               (let ((close-pos (find-matching-weird open-pos open-symbol close-symbol body)))
                 (if close-pos
                     (weird-forms open-symbol close-symbol
                                  `(,@(subseq body 0 open-pos) 
                                      (,@(subseq body (1+ open-pos) close-pos))
                                      ,@(subseq body (1+ close-pos))))
                     (error "unmatched weird brackets")))
               (if (find close-symbol body :test #'symbol-name-equal)
                   (error "unmatched weird brackets")
                   (loop for item in body collect 
                        (weird-forms open-symbol close-symbol item))))))
        (t body)))


(defmacro with-weird-forms ((open-symbol close-symbol) &body body)
  `(progn
     ,@(weird-forms open-symbol close-symbol body)))

So there are a few parts to this.

First we have (symbol-name-equal), this is a helper function because we are now using symbols and symbols belong to packages. symbol-name-equal gives us a way of checking if the symbols have the same name ignoring what package they reside in.

Second we have (find-matching-weird). This is a function that takes a list and and index to an opening weird bracket and returns the index to the closing weird bracket. This makes sure we get the correct bracket even with nesting

Next we have (weird-forms). This is the juicy bit and what it does is to recursively walk through the list passed as the 'body' argument and do the following:

  • If body is an empty list just return it
  • if body is a list then
    • find the positions of our open and close symbols.
    • if only one of them is found then we have unmatched brackets.
    • if we find both symbols then make a new list with the bit between the start and end positions inside a nested list.
    • we then call weird forms on this result in case there are more weird-symbol-forms inside.
  • there are no weird symbols then just loop over the items in the list and call weird-form on them to keep the search going.

OK so that function transforms a list. For example try:

(weird-forms 'a 'b '(1 2 3 a 4 5 b 6 7))

But we want this to be proper lisp code that executes so we need to use a simple macro. (with-weird-forms) is a macro that takes calls the weird-forms function and puts the result into our source code to be compiled by lisp. So if we have this:

(with-weird-forms (a b)
  (+ 1 2 3 a - a + 1 2 3 b 10 5 b 11 23))

Then it macroexpands into:

(PROGN (+ 1 2 3 (- (+ 1 2 3) 10 5) 11 23))

Which is totally valid lisp code, so it will run!

CL-USER> (with-weird-forms (a b)
           (+ 1 2 3 a - a + 1 2 3 b 10 5 b 11 23))
31

Finally if you have settled on the 'a' and 'b' brackets you could write another little macro:

(defmacro ab-lang (&rest code)
  `(with-weird-forms (a b) ,@code))

Now try this:

(ab-lang a let* a a d 1 b a e a * d 5 b b b a format t "this stupid test gives: ~a" e b b)

Cheers mate, this was great fun to write. Sorry for dismissing the problem earlier on.

This kind of coding is very important as ultimately this is a tiny compiler for our weird language where symbols can be punctuation. Compilers are awesome and no language makes it as effortless to write them as lisp does.

Peace!

Geddes answered 20/10, 2013 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.