Hacker News new | ask | show | jobs
by ventana 12 days ago
Translating a language into a different language is a popular thing to do these days, but still not a very easy one. I feel it's like peeling an infinite onion of misery. First, you write a parser of your source language, figure out the translation of the instructions, and emit the code in the target language, and you're very happy when your translated Hello world compiles.

Then, a user (like me) tries writing something like

  package main
  
  func main() {
    register := 42
    println(register)
  }
well, oops.

  /tmp/solod_build3904763637/main.c: In function 'main':
  /tmp/solod_build3904763637/main.c:6:21: error: expected identifier or '(' before '=' token
      6 |     so_int register = 42;
        |                     ^
  /tmp/solod_build3904763637/main.c:7:29: error: expected expression before 'register'
      7 |     so_println("%" PRIdINT, register);
        |                             ^~~~~~~~ (exit status 1)
OK, now you grab the list of reserved words of the target language, which is not always an easy thing to do, and rename type names and variables as needed.

The next bad thing is when you step on your own toes and see that the new names you invented, like `so_int` or `so_println`, will inevitably pollute the global namespace. We'll either cross our fingers and hope that no one will create a variable named `so_int`, or we'll need to add all our new kind-of-reserved words to our already big list of exceptions.

I'm sure there are multiple levels of complexity beyond this.

Not trying to say that seeing a bunch of new translators from language A to language B is bad: not at all! It really seems that this is one of the popular usages of the agents, and a rewarding one. But doing it without hidden bugs is kinda hard.

5 comments

> The next bad thing is when you step on your own toes and see that the new names you invented, like `so_int` or `so_println`, will inevitably pollute the global namespace. We'll either cross our fingers and hope that no one will create a variable named `so_int`, or we'll need to add all our new kind-of-reserved words to our already big list of exceptions.

To be fair, it's not like it's a completely foreign concept; consider C's categories of reserved identifiers (e.g., no double underscores or underscore followed by a capital letter) as well as the identifier ugilification that needs to be done in the C/C++ stdlibs to avoid collisions with user code.

In that vein, the transpiler could pick some prefix which users are highly unlikely to use and document that said prefix is off limits (e.g., "You can't use identifiers starting with `_solod_id_`") or maybe it could use C's reserved IDs (idk if such a transpiler would count as an "implementation" as far as the C standard is concerned).

That seems like a brittle approach to transpilation. A transpiler should translate all of the language's semantics, including resolving identifiers as symbols, and then choose legal names for them in the target language. Also, there is so much more to a language than its surface syntax.

I've never designed a transpiler (I'm not a programmer), but surely the correct approach is to transform the source code into its type-checked AST, and then translate to the target language from there?

> A transpiler should translate all of the language's semantics, including resolving identifiers as symbols, and then choose legal names for them in the target language.

Yes, it should; but it also makes things much more difficult. This specific transpiler indeed builds an AST, but does not care about identifiers, just emitting them as is [1] (I hope I found the correct place in the code).

[1]: https://github.com/solod-dev/solod/blob/8485bc867ae0f0269d75...

There is a lot of confusion around those words. I personally make a distinction between a "compiler" and a "transpiler".

A compiler preserves the semantics - if you compile Scheme to C, you get a real Scheme program in C, with full semantics.

A transpiler does not have to guarantee that. It is much easier to transpile Python to JavaScript than it is to truly compile Python to JavaScript. A transpiler can tolerate some amount of leakage between worlds (think 'arguments' as a variable name in Python vs JS).

It really depends on your needs. Sometimes a transpiler is okay, sometimes totally inadequate.

I'm really sorry to be harsh but if you don't use programming languages and have never written a transpiler, why should anyone read your comment?

You critique the approach as brittle and yet the approach you propose doesn't solve the thorny problem gp described of a growing ball of reserved keywords.

I've written multiple transpilers and their comment is how I do it. Safely mapping identifiers is one of those many edge cases you discover as you go along
You may have missed this in their comment:

> resolving identifiers as symbols, and then choose legal names for them in the target language

The responsibility for avoiding collision with the target language is in the transpiler; it should mangle identifiers appropriately to ensure you can't accidentally hit a keyword in the target language.

They technically didn't say that they don't use programming languages, they said they're not a programmer. There's plenty of other people who use programming languages regularly, but not necessarily because they're into them.
> I'm really sorry to be harsh but if you don't use programming languages and have never written a transpiler, why should anyone read your comment?

Lots of reasons. If you can't come up with one then you're not the intended audience, and that's fine.

I made the comment for several reasons. Here's two: (1) I had high confidence that my understanding is correct and wanted to share my knowledge with others. (2) In case my understanding wasn't correct I was hoping someone would correct me, resulting in both me and others learning some interesting nuance.

As for why you in particular should read my comment: Curiosity. There are few traits that are as important to strategic thinking, creativity and success in reaching ones goals as curiosity. When I see something that doesn't map to my understanding of the world my reaction is to wonder whether I'm missing something. It has served me incredibly well so far.

It's kind of hilarious isn't it?

"I don't even write code, yet here is my technical assessment of how a tricky software problem should work".

Are you suggesting Ada Lovelace isn't capable of reasoning about a problem?

You can come up with an algorithm or system without knowing programming. Presumably we can agree the opposite, that being a programmer doesn't give you mystical insight into every programming problem.

Ultimately this isn't even a programming problem.

Timmy writes homework, Jimmy writes homework. They can name the homework however they want. Both go in the same pigeon hole. How can the teacher tell who did which homework?

> It's kind of hilarious isn't it?

It is. I too find my competence profile a bit weird.

I've been interested in security since I was 12 yet never worked full time as a software developer. I have done some coding, but that was over a decade ago, and even then it wasn't very much.

On the other hand I understand the theoretical foundations of formal languages quite well, as well as the engineering required to implement them. I'm comfortable talking about any stage of the compiler pipeline, I can recite the Lambda cube and which logics the interesting corners correspond to, and I'm designing a family of programming languages in my free time.

I understand digital design fairly well too for that matter.

Oof that's not a good look. You can trivially avoid this by just prefixing all variable names with a salt. By the way this (or a variant thereof) is called "avoiding unwanted name capture" if you want to sound all sophisticated :)
I think maybe the idea of all these transpilers is to keep the generated code readable and as close to the original code as possible. Mangling names into unreadable garbage is not difficult, but that would also make the the transpiler act more like an obfuscator, which is probably what the authors want to avoid.

Protobuf has similar problem: whatever names you use for your proto messages should be translated to the target language as is, and with multiple target languages, chances are that you'll hit a keyword in one of them; each language plugin should figure out how to resolve this.

  $ cat test.proto
  syntax = "proto3";
  
  package test;
  
  message TestMessage {
    int32 register = 1;
    int32 foo = 2;
  }
  
  $ protoc --cpp_out=. test.proto
  
  $ grep 'register\|foo' test.pb.cc | head -n 2
        : register__{0},
          foo_{0},
(I may understand `register__`, but why `foo_`? no idea)
C++ compilers do this too, thats why so many to-be-linked symbols start with "Z"
Not only for the conflicts, but mostly to encode the parameter types into the name for method overloading, because the linker has no idea about it.
A good test is to try to port significant from c++ to plain and simple C using "AI". Maybe it can retain some of the semantics for the original code.

There is a more brutal option: AI could help write a c++ to plain and simple C transpiler... then we would lose the semantics of the original program, but free ourself from the very few toxic and real-life c++ compilers out there.

> but free ourself from the very few toxic and real-life c++ compilers out there.

Funny because the canonical C++ compiler for the first decade of the language's life did compile to C. People eventually moved on to other compilers to free themselves from having to deal with the toxicity of C output.

What's old is new again.

You are an odd one: we all know that c++ syntax complexity is beyond salvation : it has reach such an absurd and grotesque level, we are in mental pathology realm: Rube Goldberg Machine accute syndrome. And it is not even a matter of argumentation, unless being of accutely bad faith.

It seems you are failing to see that this is this very syntax complexity which makes most of this toxicity.

And having a modern c++ transplier to plain and simple C, would re-open the door to alternative and 'real-life' small/medium compilers, not like those vendor/developer-locked very few options we have today.

Somebody did it for microsoft rust. Would just be a good idea for nowadays c++.

I'm actually working on one... well, it's a lot more than a C++ to C transpiler, but it's just one of the features it contains. It's actually a C/C++ derived language which currently aims to fully support C23 and C++17 in addition to its own extensions which bring in features from other languages like PHP, Rust, Python, Perl, Ruby, JS, etc, as well as auto-including headers, and auto-resolving namespaces. It can JIT execute and also generate executables, as well as emit standard C code you can compile with GCC or CLANG.

https://github.com/derekbsnider/madc

> You are an odd one

Well, we are talking about programming languages after all. That goes without saying. One has to be odd to want to do that. But we wouldn’t have it any other way!

> A good test is to try to port significant from c++ to plain and simple C using "AI". Maybe it can retain some of the semantics for the original code.

So updating cfront for modern C++.

Sanity and common sense are starting to fight back?
Add prefix 'noso_' to user variables.

So even if someone creates 'so_int' it becomes 'noso_so_int'