Live data from Hacker News

My first fifteen compilers

composition.al

41–50 of 77 posts

Re: My first fifteen compilers

#41
post #30
post #20

Earlier quoted context omitted.

I disagree. I tried that approach for many years but without external input, I could never figure out how to transition from a simple expression language to a proven, working compiler architecture. While large compiler architectures work well for smaller languages, the opposite is not true. I have found it much better to pick a good introductory text and just work through the exercises.

You can turn an interpreter into a compiler by replacing all code that actually does something by code that prints out the code that does it in the target language. It takes a bit to wrap your head around it, and you won't get an optimizing compiler, but a compiler it will be. So your values are no longer values in the interpreter's language, but descriptions in the target language for getting that value. To compile…

Since I'm not sure how understandable the explanation was, here is an example, a simple interpreter for a Lisp-like language, using Python lists as the syntax tree.

    def interpret(state, function_table, expression):
        function = function_table[expression[0]]
        return function(state, function_table, *expression[1:])
    
    def interpret_all(state, function_table, expressions):
        return [interpret(state, function_table, e) for e in expressions]
    
    interpreter_table = {
        '+': lambda s, ft, *es: reduce(lambda a, b: a + b, interpret_all(s, ft, es)), # sum
        '*': lambda s, ft, *es: reduce(lambda a, b: a * b, interpret_all(s, ft, es)), # product
        '!': lambda s, ft, k, v: s.update({k: interpret(s, ft, v)}),                  # set variable
        '?': lambda s, ft, k: s[k],                                                   # get variable
        ';': lambda s, ft, *es: interpret_all(s, ft, es)[-1],                         # execute a sequence and get the last value
    }
An example program for 'x = a * (b + a), return x * x' looks like this:

    program = [';',
                ['!', 'x', ['*', ['?', 'a'], ['+', ['?', 'b'], ['?', 'a']]]],
                ['*', ['?', 'x'], ['?', 'x']]]
You can execute the interpreter with initial values for a and b like this:

    >>> interpret({'a': 10, 'b': 20}, interpreter_table, program)
    90000
The compiler can be implemented by swapping out the interpreter_table with different functions to print code instead of executing it:

    def fresh_variable(state):
        next_tmp = state.get('__next_tmp__', 0)
        var = 'tmp_%s' % next_tmp
        state['__next_tmp__'] = next_tmp+1
        return var
    
    def compile_sum(state, a, b):
        var = fresh_variable(state)
        print(var+' = '+a+' + '+b)
        return var
    
    def compile_product(state, a, b):
        var = fresh_variable(state)
        print(var+' = '+a+' * '+b)
        return var
    
    def compile_set(state, key, value):
        state[key] = key
        print(key+' = '+value)
    
    compiler_table = {
        '+': lambda s, ft, *es: reduce(lambda a, b: compile_sum    (s, a, b), interpret_all(s, ft, es)), # sum
        '*': lambda s, ft, *es: reduce(lambda a, b: compile_product(s, a, b), interpret_all(s, ft, es)), # product
        '!': lambda s, ft, k, v: compile_set(s, k, interpret(s, ft, v)),                                 # set variable
        '?': lambda s, ft, k: s[k],                                                                      # get variable
        ';': lambda s, ft, *es: interpret_all(s, ft, es)[-1],                                            # execute a sequence and get the last value
    }
Then you can easily compile the program:

    >>> interpret({'a': '10', 'b': '20'}, compiler_table, program)
    tmp_0 = 20 + 10
    tmp_1 = 10 * tmp_0
    x = tmp_1
    tmp_2 = x * x
    'tmp_2' # this is the return value, telling you where to find the result
Depending on the syntax of your target language, control flow might require additional compiler state. In Python, I would have to store the current indentation level. If someone is interested, I could show how to do if-statements.

Re: My first fifteen compilers

#42

Earlier quoted context omitted.

> If, on the other hand, the output is meant to be developed further by hand, if the output is considered an equivalent and not lower representation, then it's a transpiler. Which is also still a compiler.

I would argue that it isn't, if it is, then what's the distinction between a compiler and a transpiler?

If all birds are dinosaurs, then what's the distinction between a dinosaur and a bird? Well, there used to be dinosaurs who weren't birds.

Likewise, there are compilers I wouldn't call transpilers.

Re: My first fifteen compilers

#44
post #42

Earlier quoted context omitted.

I would argue that it isn't, if it is, then what's the distinction between a compiler and a transpiler?

If all birds are dinosaurs, then what's the distinction between a dinosaur and a bird? Well, there used to be dinosaurs who weren't birds. Likewise, there are compilers I wouldn't call transpilers.

So you suggest that transpilers are a subset of compilers, where I suggest that they are disjoint sets. No need for snark.

Re: My first fifteen compilers

#45
post #21

Earlier quoted context omitted.

If people find getting started on a compiler to be a bit too intimidating, one good way to get your feet wet is implementing an interpreter for small subset of a language. Perhaps the basic arithmetic part of adding/multiplying/dividing integers.

And ideally, your compiler will turn that interpreter into a compiler :)

Close parallels to partial evaluation and the Futamura projections. https://en.wikipedia.org/wiki/Partial_evaluation#Futamura_pr...

Re: My first fifteen compilers

#46

I've always thought of compilers as usually lossy graph rewriters with input and output data structures usually being 'flatter' in some sense. Maybe a both simplistic and vague model, but it has served me well enough the few times I needed to build one. This isn't meant to validate or invalidate any other view or definition, but I'm curious if there are any good counter examples or theoretical reasons for characteris…

Graph rewriting turns out to be a rather difficult problem, and many optimizations are better recast in other frameworks. For example, instruction scheduling (on superscalar processors) is pretty much a textbook example of the job scheduling problem. Loop optimizations can be most easily expressed in the polyhedral loop optimization model. Decisions like inlining can be framed as high-dimension, highly non-linear, multi-goal optimization (in the mathematics sense) problems.

Re: My first fifteen compilers

#47
As a favor for a friend, I wrote a mini-"compiler" that translated detailed specs for story game scenes into code. I had never done anything similar (my background is more on the Math/Stats side) but I figured hey, what the hell.

There were only a few types of possible scenes, so my first approach was to create a data structure for each type of scene that had a method converting it to code. However, this broke badly with conditional/branching paths that could potentially have arbitrary levels of nesting.

So my next approach was to create multiple passes using some simple recursive data structures "Parseables" where conditional branches could contain other Parseables (including other conditional branches) as well as multiple passes (Text -> Parseable -> Printable -> Code instead of Text -> Screen -> Code.) This worked quite nicely.

Had I realized this was a compiler, I could have probably read some tutorials and not had to do everything from scratch. This would probably have resulted in better engineering, but been a lot less fun.

My amateurish code, if anyone is curious: https://github.com/Satvik/spec-compiler

Re: My first fifteen compilers

#48
post #8

Earlier quoted context omitted.

Googling "what is a compiler" returns this: "a program that converts instructions into a machine-code or lower-level form so that they can be read and executed by a computer." So, that's something. It's hard to get more canonical than the Dragon Book. The Dragon Book (2nd Ed.) says this in section 1.2: "Up to this point we have treated a compiler as a single box that maps a source program into a semantically equivale…

There's also the etymology, ie. the pre-computing dictionary definition: you compile eg. a list, ie. make something smaller/shorter from a larger input. You also write a book when it's an original work, but another author or editor might take parts of yours and other books and compile an anthology. You might translate a book from one language to another, but that's not considered a compilation.

I've always been under the impression that compile means "put together" rather than "compress".

Re: My first fifteen compilers

#49

I wonder if scheme-based compiler courses are still run at Indiana University? Abdulaziz Ghuloum's 'Incremental compiler construction' [1] also has a working compiler at the end of each stage. For example after the first week you have a compiler that outputs a program that prints a single integer, the 2nd week immediates. The tutorial is at [2]. It doesn't use a nanopass framework, just builds the complexity of the l…

That paper is amazing.

Plug for my course, which is built around the ideas in Ghuloum's paper, and I've talked about before on HN:

https://news.ycombinator.com/item?id=13207695 https://news.ycombinator.com/item?id=15005853

Re: My first fifteen compilers

#50
post #42

Earlier quoted context omitted.

If all birds are dinosaurs, then what's the distinction between a dinosaur and a bird? Well, there used to be dinosaurs who weren't birds. Likewise, there are compilers I wouldn't call transpilers.

So you suggest that transpilers are a subset of compilers, where I suggest that they are disjoint sets. No need for snark.

"Transpiler" is short for "transcompiler". Has been since the 80's.

And what verb do you use with a transpiler? It compiles one form into another.

A transpiler is a source-to-source compiler. What you do with the output afterwards hardly matters, when it is performing the act of compilation.

There is no distinction here. One is merely a subset of the other. Which is good for communicating purpose, but you can't just assume one is seperate from the other when they employ the same process.

A compiler may not compile to a source language, though it might.

A transpiler is a compiler that compiles to a source language.

Post reply on HN