how to make newlisp into another language

For the Compleat Fan
Locked
TedWalther
Posts: 608
Joined: Mon Feb 05, 2007 1:04 am
Location: Abbotsford, BC
Contact:

how to make newlisp into another language

Post by TedWalther »

Hi. I want to do something special with NewLISP. I've been told that it is bad procedure, or almost impossible to do in other versions of LISP.

I want to integrate the Lilypond minilanguage into Newlisp.

I want to have a special function (lilypond ) and everything inside the brackets is interpreted differently. The definition of "symbol" changes, etc. I want newlisp to interpret it like this:

Take this:

(lilypond-notes
\sacredHarpHeads \set autoBeaming = ##f
g2. d'2 d4 | d (c) b a2 g4 | a2. b | g2 g4 b2 a4 | b (c) d e2. | g d2 e4 | d (c) b a2. |b g2 g4 | d' (b) a b2.~ | b
)

And I want newlisp to treat it like this:

[text]
\sacredHarpHeads \set autoBeaming = ##f
g2. d'2 d4 | d c b a2 g4 | a2. b | g2 g4 b2 a4 | b c d e2. | g d2 e4 | d (c) b a2. |b g2 g4 | d' (b) a b2.~ | b
[/text]

However, there is a difference. I also want this to work:

(lilypond-notes g2. d'2 d4 | d c b (slur a b c))

should output

[text]g2. d'2 d4 | d c b (a b c)[/text]

That is, I want "slur" to be a macro that expands to something like (print (append "(" (lilypond-notes a b c) ")"))

And I want to be able to incorporate other lisp things in too.

So (lilypond-notes a b (tempo 60)) should output "a b \tempo 60 4" So the (tempo) function is just normal lisp, and symbols etc are interpreted as normal.

So I want to be able to have custom functions that interpret symbols differently, and still interleave and mingle functions of newlisp code.

Final example of what I want to do:

(define (tempo bpm) (push bpm *bpm*) (print "\tempo " bpm "4"))

(lilypond-notes a'4 b2. (fermata c) d) should output this:

"a'4 b2." (tempo (/ (pop *bpm*) 2)) "\fermata c" (tempo (* (pop *bpm*) 2)) "d"

and this would result in:

[text]
a'4 b2. \tempo 40 4 \fermata c \tempo 80 4 d
[/text]

Any ideas how to do this? This minilanguage would save a lot of typing, and associated typos, in the music typesetting work I do.

Ted

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

What you are showing in your post, reminds me of the work for Postscript http://newlisp.org/index.cgi?Postscript and newLISP.

Basically you want to define a Lisp like mini language which outputs Lilypond.

Look at the file http://newlisp.org/syntax.cgi?postscrip ... pt-lsp.txt scroll down beyond the gray colored the comment section and beyond the green colored postscript section, and you see a lot of newLISP definitions translating a function call with parameters into Postscript output.

Very often you pass musical notes, i.e. 'g2 b4' etc.; in this case it may be useful to use 'define-macro', to avoid evaluating these symbols, but taking them literally.

Often the number of of arguments you pass may be variable, in this case the 'args' and 'doargs' functions may be useful for you for iterating through an unknown number of parameters.

For formatting Lilypond output the 'format' function is practical.

When expressing string containing the backslash \ you have to precede the backslash with a second backslash when using quotes "" as delimiters, i.e.:

Code: Select all

"\\sacredHarpHeads \\set autoBeaming"
you can avoid the double backslash by using curly braces as string delimiters or using [text],[/text] tags (as in your post):

Code: Select all

{\sacredHarpHeads \set autoBeaming}
But the most important recommendation is this: the new language you define should express things on a higher abstraction level, let Lilypond do the hard low level work of generating the sheet music and use newLISP to build higher level functions of sheet-music generation.

Hope this helps a little,

Lutz

TedWalther
Posts: 608
Joined: Mon Feb 05, 2007 1:04 am
Location: Abbotsford, BC
Contact:

Post by TedWalther »

Thanks Lutz. I may just have to intersperse {} strings with newlisp code. Not quite the minilanguage I wanted, but close enough to be an improvement over what I have right now.

What would it take to make a top-level function (string ...) that was a "special" function the way (lambda) is a special function? Or (list) ? Just as (list ...) returns its elements as a list, (string ...) would return everything up to the closing paren as a string? So, (string foo bar baz) would return the string "foo bar baz".

I think with a function like that, I could do everything I want in the way of a minilanguage.

Although, inside the (string ...) function, parenthesis would have to be backslash escaped, so that newlisp code could be embedded, as in this example:

(string a + b + c = (+ a b c))
=> "a + b + c = 3"

Ted

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

Hi Ted - interesting looking project you've got there (and Lilypond has a built-in Scheme interpreter too...?!)...! I haven't investigated it much but newLISP can be a useful tool for you.

If you want to do this:

Code: Select all

(set 'a 1 'b 2 'c 3)
(string! a + b + c = (+ a b c))
;-> a+b+c=6
where a function called string! compiles a string and evaulates any lists, then this is one way:

Code: Select all

(define-macro (string!)
  (let
    ((result {}))
    (doargs (t)
      (cond
        ((list? t)   (push (string (eval t)) result -1))
        ((symbol? t) (push (string (name t)) result -1))
      ) 
    )
  result
  )
)
Of course, what happens when eval fails (eg if I hadn't defined a, b, and c first)?

TedWalther
Posts: 608
Joined: Mon Feb 05, 2007 1:04 am
Location: Abbotsford, BC
Contact:

Post by TedWalther »

Thanks Cormullion. Your code looks close to what I want.

Two issues:

1) I'd like to preserve whitespace between elements of the string, especially carriage returns.

2) Even if whitespace can't be preserved, I'd like to have "symbols" that contain any character at all except for the "(" character, unless it is backslash escaped, like "\(".

I guess this involves some modifications to the reader function. Is that possible in newlisp?

Ted
Last edited by TedWalther on Tue Sep 04, 2007 5:07 pm, edited 1 time in total.

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

Ahh. (He said ominously.)

I'm not sure exactly what you mean by the second point, but I think I can see the big problem with my suggestion. By using the built-in newLISP parser I'm ignoring whitespace altogether. So you'll probably have to pass a real string and parse it into the desired 'tokens' yourself...

It might be easier than it sounds...

TedWalther
Posts: 608
Joined: Mon Feb 05, 2007 1:04 am
Location: Abbotsford, BC
Contact:

Post by TedWalther »

For the (string ...) function, I'd like the reader to evaluate the first argument in the list before proceeding. Then if it is a special, like string, it would "read" the rest of the string appropriately. So, for string, when the reader sees "(string " then it would kick into a mode where everything up to the next ) is shoved into a string. Except that a ( inside the string would kick it back into regular mode.

Ted

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

That's one way. Or perhaps you could investiage a simpler way:

Code: Select all

(define (string! s)
    (replace {(\(.*?\))} s (string (eval-string $1)) 0))

(set 'a 1 'b 2 'c 3) 

(string! {a  +  b + 
c = (+ a b c)}) 

;->
a  +  b + 
c = 6
and you're keeping your white space.

I don't think you can get parentheses into symbol names, though, unless you enclose them with brackets.

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

I don't think you can get parentheses into symbol names
you could, but its a bit clumsy ;-)

Code: Select all

> (set (sym "(") 123)
123
> (eval (sym "("))
123
>

Lutz

TedWalther
Posts: 608
Joined: Mon Feb 05, 2007 1:04 am
Location: Abbotsford, BC
Contact:

Post by TedWalther »

cormullion wrote:That's one way. Or perhaps you could investiage a simpler way:

Code: Select all

(define (string! s)
    (replace {(\(.*?\))} s (string (eval-string $1)) 0))

(set 'a 1 'b 2 'c 3) 

(string! {a  +  b + 
c = (+ a b c)}) 

;->
a  +  b + 
c = 6
and you're keeping your white space.

I don't think you can get parentheses into symbol names, though, unless you enclose them with brackets.
Thanks, I think we're getting closer. What if the parens are nested?

(string! {a b c = (+ a (* b c))})

The eval function will work correctly, but will that regex match up the proper parens? Is there a regex somewhere that matches matching parens even if there are interior paren sets?

Ted

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

here is something else which might help:

http://newlisp.org/code/modules/infix.lsp.html

this translates infix expressions into Lisp like expressions. Click on source link in the upper left. You could modify this (priority grammatic stack parser) to translate into Lilypond statements instead of newLISP expressions.

Lutz

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

You can do most things with regex (lookahead assertions, etc) but it just gets baffling and silly after a while. Sometimes there's no alternative, though.

A simple string splitter might work better, perhaps:

Code: Select all

(define (string! _s)
  (let
   (_level 0
    _lisp-buffer {}
    _text-buffer {}
   )
    (dolist (_c (explode _s))
      ; check for parentheses
      (cond
        ((= _c {(})   ; start a newLISP expr
            (inc '_level))
        ((= _c {)})
            (dec '_level)
        )
      )
      (if (and (!= _c {)}) (= _level 0))
        (push _c _text-buffer -1)   ; accumulate the text
        (push _c _lisp-buffer -1)   ; or accumulate the newLISP
      )
    (and 
        (= _level 0)
        (= _c {)})
        ; last one, time for evaluation
        (push (string (eval-string _lisp-buffer)) _text-buffer -1)
        (set '_lisp-buffer {})
      )
    )
  _text-buffer
  )
)

(map set '(a b c d e f) (sequence 1 6))
(println (string! {if a + b + c = 
(+ a (* b c)), and 
d + e + f = (* a b c), then 
(sym b) + (sym b) = (+ b b) too}))

;->

if a + b + c = 
7, and 
d + e + f = 6, then 
2 + 2 = 4 too

Well, it's not very Lisp-y, but it might do until something better comes along. Notice that the newLISP inline code is evaluated in the 'context' of the function, so I've been a bit cautious about symbol names - there might be a better solution for this.

And I'm not sure about whether you should try and use symbol names containing parentheses. Perhaps you could use something other than backslashed parentheses and replace them...?

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

Notice that the newLISP inline code is evaluated in the 'context' of the function, so I've been a bit cautious about symbol names - there might be a better solution for this.
looking at the code, there shouldn't be any problem with symbols generated by 'eval-string' beeing the same as used in the function itself. The underscores can be omitted without a problem.

Lutz

Locked