Arc Forumnew | comments | leaders | submitlogin
Arc interpreter in coffee-script
4 points by hasenj 4823 days ago | 6 comments
Inspired partly by BiwaScheme[0], I wrote an interpreter for Arc in coffee-script.

It's on github, but no README yet.

https://github.com/hasenj/jsarc

It supports quasiquatation, special syntax (such as 'a.b!c, 'a:b~c), and macros.

You can run a REPL by executing ./jsarc

Here's a sample session

    jsarc> 'a
    a
    jsarc> 'a.b
    a.b
    jsarc> (ssexpand 'a.b)
    (a b)
    jsarc> '`(a b c ,d ,@e)
    (quasiquote (a b c (unquote d) (unquote-splicing e)))
    jsarc> ((fn (a b) (/ (+ a b) 2)) 10 20)
    15
    jsarc> (mac def (name args . body) `(= ,name (fn ,args ,@body)))
    <macro>
    jsarc> (def avg (x y) (/ (+ x y) 2))
    <lambda>
    jsarc> (avg 20 10)
    15
    jsarc> ((list 1 2 3 4 5 6) 4) # indexing
    5
Needless to say, it's no where near usable yet. Only the very basics are implemented.

I'm not too sure where this could go. Initially I wanted to see if I could make it so that I can write Node.js apps using Arc. I'm not too sure this is possible (since it's an interpreter, not a compiler). One problem is that Arc data types don't directly map to javascript types.

[0] http://www.biwascheme.org/



1 point by Pauan 4822 days ago | link

Psst, there's already at least two Arc to JS compilers out there. You could try one of them, like mine[1]. On the other hand, an interpreter can do things that a compiler cannot. So, I think it would be good to have a de facto Arc to JS compiler, and a de facto Arc to JS interpreter. Both are useful, for different situations.

---

P.S. What do you mean when you say the ssyntax 'a:b~c is supported? ~ isn't an infix operator.

---

P.P.S. If you do decide to use my compiler, and you find a bug, or something that is missing that you need, by all means tell me about it and I'll try to patch it up.

---

* [1]: https://github.com/Pauan/ar/blob/lib/arc2js.arc

-----

2 points by Pauan 4822 days ago | link

Oh, I just looked at your brainstorm file. I would like to note that arcfn is out of date. I believe it references Arc 2, not Arc 3.1. I'm sure that myself or others can help you out if you have any questions. One of the changes between Arc 2 and Arc 3 is that Arc 3 uses "assign" whereas Arc 2 uses "set".

---

I would also like to comment on "var" vs. "let". My arc2js compiler provides "let", just like Arc, and also provides a "var" special form. I have found both to be useful, so I think both should be provided. And because they behave differently, it's somewhat tricky (and pointless) to implement one in terms of the other.

If you do implement a "var" form (I think you should), I recommend the following:

1) There should be no var hoisting. This means that in the following code:

  (fn ()
    (foo a)
    (var a 10)
    a)
It should call "foo" with the global value of "a", then bind the local variable "a" to 10, then return the local binding. This is different from how "var" behaves in JS, where all "var"s are "hoisted" to the top.

---

2) It should behave like "let", allowing you to capture global variables. Consider this code:

  (fn ()
    (var a a)
    a)
In JS, that will set the variable "a" to undefined. I think this is incorrect behavior. Instead, it should take the global value of "a" and then bind it to the local variable "a". This is how "let" behaves.

---

3) This is somewhat of a nobrainer, but "var" should accept more than two arguments, just like "=":

  (fn ()
    (var a 10
         b 20)
    (list a b))
The above binds the local variable "a" to 10, and the local variable "b" to 20. It should also accept an odd number of arguments, with the default being nil.

---

4) It should be possible to use "var" as an expression. Everything in Arc is an expression, and I find that simplicity to be a wonderful property. "var" should follow it:

  (fn ()
    (if a (var b 10))
    b)
The above will return either "10" or the global value of "b" depending on whether "a" is truthy or not. Note that it should only bind the local variable "b" when the "if" expression is actually evaluated.

---

My compiler handles the 1st, 2nd, and 3rd points correctly, but cannot handle the 4th correctly, because it is a compiler, not an interpreter. To be more specific, you can use "var" as an expression, but this...

  (fn ()
    (if a (var b 10))
    b)
...is rewritten into this:

  (fn ()
    (var b)
    (if a (= b 10))
    b)
That is the best I can do, with a compiler, unless I start getting into some really insane code transformation rules.

-----

1 point by Pauan 4822 days ago | link

I would also like to answer some questions you had in the brainstorm file.

---

"besides quasiquoting, a big challenge will be implementing closures"

If you have first-class environments, then implementing closures is a piece of cake. And since you're writing an interpreter, first-class environments make an awful lot of sense.

How it works is that every function has an environment. This environment contains two bits of information: a mapping between variables and values, and a reference to the outer environment.

Then, when looking up a variable, you start in the current environment, and if it's not found, you check in the outer environment. You then repeat this process as many times as necessary until you either find the variable, or reach the global scope.

I wrote an interpreter in Python that implements a language that is almost identical to Arc, so I can help out if you like.

---

"maybe there's no need to "declare" local variables; if you set a variable that doesn't exist, it will just be local (like coffee script)"

Probably a bad idea. Paul Graham already experimented with that with a (very) early version of Arc[1], but then realized that implicit bindings causes problems with macros. It's not a technical problem, but rather a human one: with implicit binding, it's very easy to accidentally create local bindings when you didn't want to.

You can, of course, experiment with it yourself, and perhaps you'll find a way to make it work. But personally, I like keeping binding/assignment separate. I find that it makes working with closures a lot easier. Consider this:

  (let x nil
    (def foo ()
      (map [= x _] '(1 2 3))))
As you can see, we're creating a local variable "x", and then creating a global function "foo" that has access to "x". Then we're mapping over 3 numbers, and assigning each one to the outer variable "x".

Python gets around it by having a "nonlocal" and "global" keyword. If you're wondering, here's how you would do that in Python:

  def foo():
      x = None
      def bar():
          nonlocal x
          for _ in [1, 2, 3]:
              x = _
      return bar

  foo = foo()
Thus, you must either make bindings implicit, or assignment implicit. Python chose to make bindings implicit, which works out just fine in Python, but Python doesn't have macros.

---

* [1]: http://www.paulgraham.com/arcll1.html

-----

1 point by hasenj 4822 days ago | link

The brainstorm file is never updated. I keep putting some brain dump there without cleaning the previous brain dumps.

I think closures already work; I just haven't tested them enough.

> How it works is that every function has an environment. This environment contains two bits of information: a mapping between variables and values, and a reference to the outer environment.

> Then, when looking up a variable, you start in the current environment, and if it's not found, you check in the outer environment. You then repeat this process as many times as necessary until you either find the variable, or reach the global scope.

This is exactly how I did it.

-----

1 point by hasenj 4822 days ago | link

> I would like to note that arcfn is out of date. I believe it references Arc 2, not Arc 3.1. I'm sure that myself or others can help you out if you have any questions. One of the changes between Arc 2 and Arc 3 is that Arc 3 uses "assign" whereas Arc 2 uses "set".

Thanks. I did notice ac.scm uses assign where as arcfn talks about 'set'. Anyway, I didn't implement neither assign nor set, not scar not scdr. I just implemented '=' as a special form.

Thanks for #3 btw, my '=' only worked with one variable before; now fixed.

I just added var, btw.

-----

1 point by hasenj 4822 days ago | link

> What do you mean when you say the ssyntax 'a:b~c is supported? ~ isn't an infix operator.

Ah, sorry, typo; I meant b:~c

In arc:

  arc> (ssexpand 'a:b:~c)
  (compose a b (complement c))
It works the same in my interpreter:

  jsarc> (ssexpand 'a:b:~c)
  (compose a b (complement c))
Though I don't have an implementation of compose or complement yet.

-----