how to make newlisp into another language

Started by TedWalther, September 02, 2007, 04:58:59 PM

Previous topic - Next topic

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
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

Lutz

#1
What you are showing in your post, reminds me of the work for Postscript http://newlisp.org/index.cgi?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?postscript/postscript-lsp.txt">http://newlisp.org/syntax.cgi?postscrip ... pt-lsp.txt">http://newlisp.org/syntax.cgi?postscript/postscript-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.:


"\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):


{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

#2
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
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

cormullion

#3
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:

(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:



(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

#4
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
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

cormullion

#5
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

#6
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
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

cormullion

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


(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

#8
QuoteI don't think you can get parentheses into symbol names


you could, but its a bit clumsy ;-)


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




Lutz

TedWalther

#9
Quote from: "cormullion"That's one way. Or perhaps you could investiage a simpler way:


(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
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

Lutz

#10
here is something else which might help:



http://newlisp.org/code/modules/infix.lsp.html">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

#11
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:


(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

#12
QuoteNotice 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