Common Lisp: custom predicate for conditional formatting - format

I have a list consisting of two different kind of elements:
String designators
Cons cells, whose car and cdr are both string designators
I would like this list to be printed with the following structure: all elements separated by commas, and each cons cell (a . b) is printed as a=b.
The real goal is to generate Tikz/LaTeX code using Common Lisp, and this specific part of the code is just there to generate "options" (to an environment, a package, a command ...)
Example:
>>> (format nil "<cool-format-string>" '(draw (color . red) dashed (fill . gray)))
[draw, color=red, dashed, fill=gray]
I know about iteration directives, conditional ones, etc, in format. The only problem is that I want to print my list elements differently according to their type, not according to their numerical value or their 'truth' (nil vs anything else)
Besides writing a custom directive with ~/, is there a way to use another construction which would do something akin to the conditional directive, but using a custom predicate ?

(defun dotted-pair-p (x)
(and (listp x) (not (listp (cdr x)))))
(defun print-fancy (lst)
(let ((res (mapcar (lambda (x) (if (dotted-pair-p x)
(format nil "~a=~a" (car x) (cdr x))
(format nil "~a" x)))
lst)))
;; add the ', ' inbetween the strings
;; and put the result in between "[]"
(format nil "[~{~a~^, ~}]" res)))
(print-fancy '(draw (color . red) dashed (fill . gray)))
;; "[DRAW, COLOR=RED, DASHED, FILL=GRAY]"

Related

Standard way to handle quoted symbol in lisp macros in Scheme

For some code I was working I've needed to handle 'x inside macro. What is standard way of handling those values?
I have code like this:
(define (quoted-symbol? x)
(and (pair? x) (eq? (car x) 'quote) (symbol? (cadr x)) (null? (cddr x))))
(define-macro (test x)
(if (quoted-symbol? x)
`(begin
(display ',(cadr x))
(newline))))
(test 'hello) ;; 'hello will be expanded into list (quote hello)
Is this how this should be handled, or is just in macro you don't use quoted symbols?
NOTE: I'm not asking about hygienic macros (I'm asking about real lisp macros), so please no answers with hygienic macros.
EDIT:
My macro works correctly in Guile and BiwaScheme and in my own scheme like lisp in JavaScript. Here is better example:
(define-macro (test x)
(if (quoted-symbol? x)
`',(cadr x)))
(define (example arg)
(list arg (test 'world)))
(example 'hello)
the question was not about display, but about (cadr x).
EDIT2: You've asked so here you go, my macro:
(define-macro (--> expr . code)
"Helper macro that simplify calling methods on objects. It work with chaining
usage: (--> ($ \"body\")
(css \"color\" \"red\")
(on \"click\" (lambda () (print \"click\"))))
(--> document (querySelectorAll \"div\"))
(--> (fetch \"https://jcubic.pl\") (text) (match /<title>([^<]+)<\/title>/) 1)
(--> document (querySelectorAll \".cmd-prompt\") 0 \"innerText\")"
(let ((obj (gensym)))
`(let* ((,obj ,(if (and (symbol? expr) (not (null? (match /\./ (symbol->string expr)))))
`(.. ,expr)
`,expr)))
,#(map (lambda (code)
(let ((name (gensym))
(value (gensym)))
`(let* ((,name ,(cond ((quoted-symbol? code) (symbol->string (cadr code)))
((pair? code) (symbol->string (car code)))
(true code)))
(,value (. ,obj ,name)))
,(if (and (pair? code) (not (quoted-symbol? code)))
`(set! ,obj (,value ,#(cdr code)))
`(set! ,obj ,value)))))
code)
,obj)))
;; ---------------------------------------------------------------------------------------
(define (quoted-symbol? x)
"(quoted-symbol? code)
Helper function that test if value is quoted symbol. To be used in macros
that pass literal code that is transformed by parser.
usage:
(define-macro (test x)
(if (quoted-symbol? x)
`',(cadr x)))
(list 'hello (test 'world))"
(and (pair? x) (eq? (car x) 'quote) (symbol? (cadr x)) (null? (cddr x))))
the macro is used in my scheme like lisp in JavaScript, like the doc string suggest:
(--> document (querySelectorAll ".class") 0 "innerText")
I want to support:
(--> document (querySelectorAll ".class") 0 'innerText)
The code can be tested online at: https://jcubic.github.io/lips/ (You need to copy/paste the code since current version allow only method calls).
To get expansion you can use
(pprint (macroexpand (--> document (querySelector "x"))))
if it don't work (don't expand) it mean that macro is broken somehow.
dot is build in function that get property of an object and .. macro:
(define-macro (.. expr)
"(.. foo.bar.baz)
Macro that gets value from nested object where argument is comma separated symbol"
(if (not (symbol? expr))
expr
(let ((parts (split "." (symbol->string expr))))
(if (single parts)
expr
`(. ,(string->symbol (car parts)) ,#(cdr parts))))))
that can be use to get nested property like (.. document.body.innerHTML)
Scheme doesn't have "real lisp macros". Some implementations has something similar, but the forms have different names and uses. They are not portable at all.
The standard way of handling 'x is to handle it like an expression that gets evaluated in the expansion. Eg.
(define var 'x)
(test 'x)
(test var)
The two test forms should amount to the same even though the macro test gets (quote x) in the first and the symbol var in the second. At the time of the expansion var does not exist since the implementation can expand all the macros before starting.
You implementation of test will not work. Eg. the display might be run one or twice and then each time you call a procedure that uses it it will gfail since the expansion is the undefined value and it might not be fit for evaluation. eg.
(define (example arg)
(list arg (test 'w)))
When this is defined you get 'w or (quote w) printed with a newline and then the procedure it tries to store is:
(define (example arg)
(list arg #<undefined>))
Note that what constitutes the undefined value is chosen by the implementaion, but I know for sure that in many implementaions you cannot evaluate #<undefined>.

LISP clause for and clause let ¿why?,making a programming language in racket using ragg

I have long been trying to find the error, I'm doing a programming language and have the next code, using ragg, I have a syntax-object(resto ...) what has a bracket as data, I transform this syntax-object to a datum:
(let ([i (syntax->datum #'(resto ...))])
(display "Content i:")
(display i)
(if (eq? i (string->symbol "(})"))
(display "true")
(display "false")
)
)
and the output is:
Content: (})
false
But if I do this
(for ([i (syntax->datum #'(resto ...))])
(displayln "Content:")
(displayln i)
(if (eq? i (string->symbol "}"))
(display "true")
(display "false")
)
)
and the output is:
Content: }
true
MY QUESTION:
¿WHY THE IF OF CLAUSE LET IS FALSE?
¿AS I CAN COMPARE THESE TWO TYPES AND THAT THE RESULT IS TRUE WITHOUT THE FOR?
Documentation about functions:
syntax->datum
Each piece of code is doing a very different thing, I'll show you how to make each one work. The first one uses let to assign into a variable the whole list returned by syntax->datum, and afterwards you compare it against another list (better use equal? for testing equality, it's more general):
(let ([i (syntax->datum #'(|}|))]) ; `i` contains `(})`
(display "Content i: ")
(displayln i)
(if (equal? i '(|}|)) ; a list with a single element, the symbol `}`
(displayln "true")
(displayln "false")))
The second piece of code is using for to iterate over each element in a list, until it finds the symbol }:
(for ([i (syntax->datum #'(|}|))]) ; `i` contains `}`
(display "Content i: ")
(displayln i)
(if (equal? i '|}|) ; the symbol `}`
(displayln "true")
(displayln "false")))
As a side note, you have to be very careful with the way you're going to process all those curly brackets {} in your language, they're interpreted as normal parentheses () in Racket and they'll be tricky to handle, notice how I had to escape them by surrounding them with vertical bars.

How to 'display' multiple parameters in R5RS Scheme

In R5RS Scheme how do you display multiple parameters, with a single call? my implementation below works, but adds extra parentheses and spaces.
#!/usr/bin/env racket
#lang r5rs
(define (display-all . rest) (display rest))
(display-all "I " "have " "a " "lovely " "bunch " "of " "coconuts\n")
results in
owner#K53TA:~$ ./Template.ss
(I have a lovely bunch of coconuts
)
Simplest:
(define (display-all . vs)
(for-each display vs))
Note the use of for-each instead of map - for-each is the same thing but assumes you're only calling the function for side-effects, so instead of returning a list of results (using map with display would return a list of voids) it just returns void.
This can get annoying if you want to display non-string things and have spacing between them, for instance if you want (display-all 12 "bananas") to display the string "12 bananas" you have to manually turn the number into a string and add the space yourself. It would be easier to just add spaces in-between elements of the list:
(define (insert-between v xs)
(cond ((null? xs) xs)
((null? (cdr xs)) xs)
(else (cons (car xs)
(cons v (insert-between v (cdr xs)))))))
(define (display-all . vs)
(for-each display (insert-between " " vs)))
Now calling this:
(display-all "blah" 4 "bloo")
does what you'd expect. If you don't want the spaces inserted automatically, you can specify another argument as the separator object and use it however you need. Here's a version that accepts a separator object:
(define (display-all sep . vs)
(for-each display (insert-between sep vs)))
This approach would make more sense in a version of scheme that supports optional and keyword arguments however, so you could default it to either a space or the empty string and not interfere with the rest-args.
If all the arguments are string, just use apply and string-append. both are in r5rs
#!/usr/bin/env racket
#lang r5rs
(define (display-all . rest) (display (apply string-append rest)))
(display-all "I " "have " "a " "lovely " "bunch " "of " "coconuts\n")

Function for detecting quotes in Scheme code

I am trying to write a function that can check whether or not some input is a quotation for a syntax checker.
I have the following code:
(define is-quote-expression?
(lambda (v)
(equal? (car v) 'quote)
(is-quotation? (cdr v)))))
(define is-quotation?
(lambda (v)
(or (number? v)
(boolean? v)
(char? v)
(string? v)
(symbol? v)
(null? v)
(and (pair? v)
(is-quotation? (car v))
(is-quotation? (cdr v)))))
When I try to evaluate, I get the following:
(is-quote-expression? '1)
#f
(is-quote-expression? (cons 'quote 1))
#t
I think my TA told me that the Scheme environment replaced all "'" with "'quote", however, this does not seem to be the case. We are running Petite Chez Scheme.
What am I not seeing?
Thanks in advance.
There are a couple of problems with your code, for starters the (lambda (pair? v) part in is-quote-expression? is almost certainly a mistake (you're defining a lambda with two parameters called pair? and v).
Also, I believe you intended to call is-quotation? from is-quote-expression? only if v isn't a pair, so it doesn't make sense to ask again if (pair? v) in is-quotation?. And who said that a pair is a quotation only if both its car and cdr are quotations?
Here, I believe this is what you intended:
(define is-quote-expression?
(lambda (v)
(if (pair? v)
(equal? (car v) 'quote)
(is-quotation? v))))
(define is-quotation?
(lambda (v)
(or (number? v)
(boolean? v)
(char? v)
(string? v)
(symbol? v)
(null? v))))
I agree with Óscar's post, though, is-quote-expression? accepts a list rather than a pair and returns whether it was a quotation.
(define is-quote-expression?
(lambda (v)
(and (proper-list-of-given-length? v 2)
(equal? (car v) 'quote)
(is-quotation? (cadr v)))))
Also, your original question shows some confusion as to what quote actually does. This is what really should happen:
> (is-quote-expression? '1)
#f
> (is-quote-expression? (cons 'quote 1))
#f
> (is-quote-expression? (quote 42))
#f
> (is-quote-expression? (quote (quote 42)))
#t
Note how the built-in quote procedure simply returns what it is passed. In the case of (quote 42) it simply returns 42. But (quote (quote 42)) returns (quote 42), which is what you wish to pass to is-quote-expression?.
The behavior of quote is specified in R6RS, appendix A.3. For '1, the relevant rule is 6sqv:
(store (sf1 ...) S1[ 'sqv1 ]) → (store (sf1 ...) S1[ sqv1 ])
Time to break this down.
The "S → T" notation defines one step in evaluating a term, where "S" evaluates to "T".
Since store and the sf1 non-terminals appear the same on both the left and right sides, you don't need to understand them to understand how '1 evaluates. If you need something, think of store as "storage environment" and the sfn as pairs of names and values; (store ((x 1) (y 2)) S) means the identifier x is associated with the value 1 and y with 2 when evaluating the term S.
If you're not familiar with the S[e] notation, it refers to a term with one "hole" (an term with a [] in it) filled with e. There are two related syntax elements: terms with holes (S[]) and terms with a hole filled by a value (S[e]). Holes are a little (but only a little) like unnamed variables. One important difference is that a term is allowed only one hole. Holes are perhaps best explained by example. All of the following are S[]:
(+ [] 1 2)
(list [] 'a "foo")
(cond ((= x 0) [])
((> x 0) 1)
(else -1))
Note that a hole can only appear where a sub-term is syntactically valid; [] 2) is not a term-with-hole. S[0] would be a term with 0 substituted into the hole:
(+ 0 1 2)
(list 0 'a "foo")
(cond ((= x 0) 0)
((> x 0) 1)
(else -1))
When a value is given for a hole, the term S[] is also called a "context". This comes from one of the primary uses for terms-with-holes: to match any term containing the given value. S[e] is any term that contains e as a valid sub-term, so S[] is the context that e appears in. In short, S1['sqv1] is a stand-in for any term that contains a quote.
(+ 'foo 1 2)
(list 'bar 'a "foo")
(cond ((= x 0) 'baz)
((> x 0) 1)
(else -1))
Note the second term provides two different contexts for quote-terms: (list [] 'a "foo"), (list 'bar [] "foo"). This suggests that you shouldn't think of holes too much as just being unnamed variables.
If you're wonder why context terms and holes are used, they're an alternative to recursive definitions. Without contexts, → would have to be defined recursively over the structure of terms (Scheme's grammar defines the structure). Substitution in lambda calculus is an example of structural recursion, as are any tree-processing functions you might define in Scheme (though Scheme syntax is quite different than the syntax used to define → and lambda calculus substitution).
(define (tree-depth tree)
(if (pair? tree)
(max (tree-depth (car tree))
(tree-depth (cdr tree)))
1
) )
Next, let's examine the meaning of sqv, which is short for "self-quoting values". This is a non-terminal from the Scheme grammar, given in appendix A.2.
sqv ::= n | #t | #f
n ::= [numbers]
sqv is simply a number or boolean.
All together, the 6sqv evaluation rule means that a quoted number or boolean evaluates to the number or boolean; the quote is simply discarded.
What this signifies for your homework is that you can't tell the difference between 1 and '1 with a normal function, since sub-terms are evaluated before the function is called. You'll need a macro.
Why go through all this just to say that "'1 evaluates to 1"? Firstly, answering "why" takes more than answering "what". Also, it will hopefully help you go beyond the question, learning a little bit how to read Scheme's formal semantics, give you a taste of computational theory concepts and lead to many more questions.

double in scheme

How to write a program in scheme that takes an arbitrary
sexpression consisting of integers and which returns an sexpression that is identical to
the original but with all the integers doubled?
We want a procedure that takes an S-expression as input, and outputs an S-expression with the same structure, but where each integer is doubled; generally, a procedure to map S-expressions:
(define (double x)
(if (number? x)
(* x 2)
x)))
(define (sexp-map op sexp)
...)
(sexp-map double some-sexpression)
The S-expression we get (SEXP) is going to be either an atom, in which case the result is (OP SEXP), or a list of S-expressions. We might think to map OP across SEXP in this case, but S-expressions nest arbitrarily deep. What we should actually do is map a procedure that will transform each element in the smaller S-expression with OP. Well would you look at that, that's just another way to describe of the goal of the procedure we're currently trying to write. So we can map SEXP-MAP across SEXP.
Well, no we can't actually, because SEXP-MAP needs to be called with two arguments, and MAP will only give it the one. To get around that, we use a helper procedure F of one argument:
(define (sexp-map op sexp)
(define (f x)
(if (list? x)
(map f x)
(op x)))
(f sexp))
F winds up doing all the real work. SEXP-MAP is reduced to being a facade that's easier to use for the programmer.
It sounds like what you want is to find each integer in the s-expression, then double it, while keeping the rest of it the same.
If you're not aware, s-expressions are just lists that may happen to contain other lists, and it makes sense to deal with them in levels. For instance, here's a way to print all the values on level one of an s-expression:
(define (print-level-one sexp)
(display (car sexp))
(print-level-one (cdr sexp)))
This will end up calling display on the car of every part of the s-expression.
You could do something similar. You'll need the functions integer? and pair? to check whether something is an integer, which should be doubled, or another list, which should be treated just like the top-level list.
(Note: I'm being deliberately vague because of the comment about homework above. If you just want the answer, rather than help figuring out the answer, say so and I'll change this.)
An Sexp of numbers is one of
-- Number
-- ListOfSexp
A ListOfSexp is one of
-- empty
-- (cons Sexp ListOfSexp)
So, you'll need one function to handle both of those data definitions. Since the data definitions cross-reference each other, the functions will do the same. Each individual function is pretty straight forward.
(define (double E)
(cond ((null? E) '())
((list? (car E)) (cons (double (car E)) (double (cdr E))))
((number? E) (list E))
(else (cons (* 2 (car E)) (double (cdr E))))
))
(map (lambda (x) (* x 2)) '(1 2 3 4 5)) => '(2 4 6 8 10)
What does it do? (lambda (x) (* x 2)) takes a number and doubles it, and map applies that function to every element of a list.
Edit: Oh, but it won't go into trees.
(define double
(lambda (x)
(cond ((null? x) (list))
((list? (car x)) (cons (double (car x)) (double (cdr x))))
(else (cons (* 2 (car x)) (double (cdr x)))))))
EDIT
Fixed this. Thanks to Nathan Sanders for pointing out my initial oversight concerning nested lists.

Resources