Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Joy was the inspiration for min :)

min is actually mainly an interpreted language, BUT it can actually as of recently be transpiled to Nim (which in turns generates C code which can be compiled), so you can actually create executable files from min, which is pretty cool.

Adding this form of compilation was actually really easy because in a stack-based language there's essentially one instruction: push an item on the stack... the only things I had to add was wrapping external files in functions to delay their evaluation to when they are required.

Combinators like linrec etc are no different from ordinary operators, they are pushed on the stack and they rearrange it...

Consider the following program that takes an integer as input and prints its factorial:

  args
  (compiled?)
    (0)
    (1)
  if get int
  (dup 0 ==) (1 +) 
  (dup 1 -) (*) linrec
  puts
Note how I am getting the first or second argument from the command line depending if I am running the program through the interpreter (min factorial.min 5) or as a stand-alone executable (./factorial 5).

When running the min "compiler":

  min -c factorial.min
...the following Nim code gets generated. As you can see, it's mostly just pushing items on the stack :)

  import min
  MINCOMPILED = true
  var i = newMinInterpreter("factorial.min")
  i.stdLib()
  ### factorial.min (main)
  i.push MinValue(kind: minSymbol, symVal: "args", column: 4, line: 1, filename: "factorial.min")
  var q1 = newSeq[MinValue](0)
  q1.add MinValue(kind: minSymbol, symVal: "compiled?", column: 10, line: 2, filename: "factorial.min")
  i.push MinValue(kind: minQuotation, qVal: q1)
  var q2 = newSeq[MinValue](0)
  q2.add MinValue(kind: minInt, intVal: 0)
  i.push MinValue(kind: minQuotation, qVal: q2)
  var q3 = newSeq[MinValue](0)
  q3.add MinValue(kind: minInt, intVal: 1)
  i.push MinValue(kind: minQuotation, qVal: q3)
  i.push MinValue(kind: minSymbol, symVal: "if", column: 2, line: 5, filename: "factorial.min")
  i.push MinValue(kind: minSymbol, symVal: "get", column: 6, line: 5, filename: "factorial.min")
  i.push MinValue(kind: minSymbol, symVal: "int", column: 10, line: 5, filename: "factorial.min")
  var q4 = newSeq[MinValue](0)
  q4.add MinValue(kind: minSymbol, symVal: "dup", column: 4, line: 6, filename: "factorial.min")
  q4.add MinValue(kind: minInt, intVal: 0)
  q4.add MinValue(kind: minSymbol, symVal: "==", column: 9, line: 6, filename: "factorial.min")
  i.push MinValue(kind: minQuotation, qVal: q4)
  var q5 = newSeq[MinValue](0)
  q5.add MinValue(kind: minInt, intVal: 1)
  q5.add MinValue(kind: minSymbol, symVal: "+", column: 15, line: 6, filename: "factorial.min")
  i.push MinValue(kind: minQuotation, qVal: q5)
  var q6 = newSeq[MinValue](0)
  q6.add MinValue(kind: minSymbol, symVal: "dup", column: 4, line: 7, filename: "factorial.min")
  q6.add MinValue(kind: minInt, intVal: 1)
  q6.add MinValue(kind: minSymbol, symVal: "-", column: 8, line: 7, filename: "factorial.min")
  i.push MinValue(kind: minQuotation%, qVal: q6)
  var q7 = newSeq[MinValue](0)
  q7.add MinValue(kind: minSymbol, symVal: "*", column: 12, line: 7, filename: "factorial.min")
  i.push MinValue(kind: minQuotation, qVal: q7)
  i.push MinValue(kind: minSymbol, symVal: "linrec", column: 20, line: 7, filename: "factorial.min")
  i.push MinValue(kind: minSymbol, symVal: "puts", column: 4, line: 8, filename: "factorial.min")


> Joy was the inspiration for min :)

Right on!

> min is actually mainly an interpreted language, BUT it can actually as of recently be transpiled to Nim (which in turns generates C code which can be compiled), so you can actually create executable files from min, which is pretty cool.

That is pretty cool. I just started learning Nim and I really like it so far.

> ...the following Nim code gets generated. As you can see, it's mostly just pushing items on the stack :)

It looks like you're sort of compiling the interpretation?

The last couple of days I've circled back and got some work done on compiling Joy code. (I'm using Python as the target language. I'd like to use Nim, but I don't want to learn that at the same time as I'm trying to write a compiler; with Python I have a lot of experience and the surprise factor is low. I know where I'm at with it.) I've just now got it to the point where I can compile (integer) math, binary Boolean logic, and loops and branches.

Example:

    ᅠ?- compile_function("gcd", `true [tuck % dup 0 >] loop pop`).

    def gcd(stack, expression, dictionary):
        stack = True, stack
        tos, stack = stack
        while tos:
            (v1, (v2, stack)) = stack
            stack = ((v2 % v1), ((v2 % v1), (v1, stack)))
            stack = 0, stack
            (v3, (v4, stack)) = stack
            stack = ((v4 > v3), stack)
            tos, stack = stack
        (v5, stack) = stack
        stack = stack
        return stack, expression, dictionary

    true.

As you can see, the generated Python good is not good. (E.g., it calculates "v2 % v1" twice for no good reason.) But it has the qualities of being correct and I-didn't-have-to-write-it! :)

(The compiler code is written in Prolog. It turns out that Prolog is so good for writing compilers that it's faster and easier to learn Prolog and then write a compiler in it than to try to write a compiler in some language you already know!)

This is messy work-in-progress code at this point, but if you're interested...

My notes start at line 697:

https://git.sr.ht/~sforman/Thun/tree/3b96f60f61f0a34928b9ee7...

THe code starts at line 995

https://git.sr.ht/~sforman/Thun/tree/3b96f60f61f0a34928b9ee7...

ANyway, still to do includes: better connections from step to step so the Python code isn't packing and unpacking the stack variable like crazy. Reuse "free" variable names (right now it just generates new vars as-needed, which is fine, but it keeps the values around until the end of the function call. Maybe they could be GC'd earlier, I dunno.)

That's all pretty straightforward, the tricky bit is handling all the meta-programming: as you no doubt know, a lot of Joy functions work by combining args with function fragments to make new functions which are then evaluated. For example, consider this (kinda silly) function:

    [dup] cons i
It's silly because it's equivalent to just "dup", but it illustrates the problem: how do you compile this function (or less-silly ones that work with the same meta-programming style)?

I suspect that you just have to include the interpreter and make "dynamic" calls to it from the compiled function (or include the compiler and JIT compile.) What do you think?

Cheers!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: