Arc Forumnew | comments | leaders | submitlogin
2 points by rocketnia 5241 days ago | link | parent

I hope to eventually support eval

I think the way you're going to go about it, by having (quote ...) forms be compiled, has a bit of a caveat. If you're already planning to have a!b expand to (a 'b) and compile to "a.b", then won't (js '(eval 'foo)) just result in "eval.foo"?

Maybe a!b should expand to (ref a "b") or something, where (ref a b) compiles to "a[b]" for most arguments but compiles to "a.contentsOfB" when the second argument is a literal string that counts as a JavaScript identifier. (The second case could be totally left off to make things easier; document['getElementById']('foo') is still a method call, and regular property gets and sets work too.)

All that being said, I bet you already support eval(), in a way:

  what would be    (js '(eval '(alert (eval '(+ 1 2)))))
  is expressed as  (js `(eval ,(js `(alert (eval ,(js '(+ 1 2)))))))
The difference here is just syntax sugar, IMO. (Saving parentheses is a fine goal of syntax sugar, though!)

Maybe string concatenation ... is in fact a good counterpart to quasiquotation and I should compile it...

That feature would be a bit more difficult to simulate if 'js didn't support it intrinsically. Here's a quick approach:

  (mac jswith (bindings . body)
    `(fn-jswith (list ,@(map .1 bindings))
                (fn (,(map .0 bindings)) ,@body)))
  
  (def fn-jswith (vals body)
    ; We're adding the suffix "v" to each name so that it isn't the
    ; prefix of any other name, as might happen with gs1234 and gs12345,
    ; for instance. Note that it still counts as a JavaScript identifier
    ; with this suffix.
    (withs (strnames  (map [string (uniq) 'v] vals)
            names     (map sym strnames))
      `( (fn ,names
           (eval ,(multisubst (map [list (+ "('+" _ "+')") _)] strnames)
                    (js do.body.names))))
         ,@vals)))
  
  (mac jslet (var val . body)
    `(jswith (,var ,val) ,@body))
  
  now what would be  (js '(eval `(+ 1 ,foo)))
  
  is expressed as    (js `(eval ,(jslet f 'foo
                                   (js `(+ 1 ,f)))))
  where the final form sent to 'js is
    (eval ((fn (gs1001v) (eval "'(1+('+gs1001v+'))'")) foo))
  
  or expressed as    (js:jslet f 'foo
                       (js `(eval ,(js `(+ 1 ,f)))))
  where the final form sent to 'js is
    ((fn (gs1001v) (eval "'eval(\'(1+('+gs1001v+'))\')'")) foo)
I do feel that this difference is more than sugar, since the 'foo subexpression is moved out of context.

Also, more importantly, it has a security leak I'm not sure how to fix. The call to 'subst doesn't pay attention to the meaning of what it's replacing. If an attacker is able to get a string like "gs1001v" into a forum post or username or whatever in the server data, and then that string is embedded as a literal string in JavaScript code which is processed as the body of a 'jslet, something wacky might happen, and the attacker will be in a position to arrange things so that just the wrong wacky things happen.

If you just make a way to put identifiable "holes" in the compiled JavaScript, you'll remove the need to resort to blind string substitution here. The holes could be as simple as names surrounded by delimiters which you guarantee not to appear elsewhere in the result (even in string literals); that way a string substitution approach doesn't have to be blind. The holes could help you implement 'quasiquote, and conversely, if you implement 'quasiquote, there might not be much of a need for the holes.

Additionally, there's an elegance to reserving unquote for escaping Arc code, which would be difficult to do if you compiled quasiquotation.

Well, the Arc quasiquotes are processed before 'js even sees the input, right? Here's the only problem I see (and maybe it's exactly what you're talking about):

A typical way to escape from two levels of nested Arc quasiquotes is ",',", as in `(a `(b ,c ,',d)). That constructs something that includes a (unquote (quote ...)) form, so it only works when you're sending the result somewhere where unquote-quotes don't matter (like, to be evaluated as Arc). So ideally, the 'js meanings of 'quasiquote and 'quote should have this property. I don't think this would be especially hard to guarantee, but it might be easy to miss.

(Note that if Arc's quasiquotes didn't nest, the same example would be expressed as `(a `(b ,',c ,d)), and no unquote-quote would hang around to be a problem. I'm beginning to wonder if nesting quasiquotes are a wart of Arc.)