(EXPERIMENT) Thinking about making an Ada/SPARK Compiler... with maybe some new stuff?

Hello everyone! Hope y’all are having a fantastic day/night today! :smiley:

I’ve started getting a lot of interest in Ada, and one of the things i wanted to try for fun (in the little spare time that i have lol) is to make a compiler for this language.

But one thing that i wanted to ask (since y’all know WAY more about this language than i do) if its a good or a bad idea (and why its a good/bad idea) to replace begin and end (and the variants of end: end if, end while, end (insert function name), …)with brackets/braces ({}).

So, yeah, i wanna hear what do y’all think, if this is really good or is it absolutely horrible lol

That’s it for now, and again, hope y’all are having a good/day night. Cheers! :smiley:

1 Like

Nobody will use an Ada compiler with braces, even Kotlin (I think) decided to move to “end” because braces are hard to see. And there is absolutely nothing experimental or new about adding braces.

We need an alternative compiler based around LLVM as a set of libraries for tooling similar to clang. I have wanted to build language tools whereby I can build an Ada AST, can’t currently do that. Also building a JIT Ada “compiler” into a tool which could be used to build interactive Ada programs was something I was going to do, but I can’t see it happening now.

If your interest is to make Ada look like C, don’t bother with such effort.

I would suggest maybe consider contributing to the existing HAC Ada Compiler project to help add more Ada syntax support.

Or Byron, whatever the status of that is.

My interest is not making Ada look like C, i just asked if it was a neat idea to add braces, but that’s it lmao

I personally had really good experiences reading tons of code with braces, specially when reading code of huge projects, and i also wanted to hear experiences from y’all out of curiosity, since i don’t know a lot of people that work constantly with Ada and/or SPARK. But that’s really it lmao

  1. Kotlin still uses braces lol, the only times it doesn’t is if a function has one line, where you can use =.
  2. I wanna know why braces are hard to see for you. Have you worked in projects where using a language with braces was terrible? Is it because you personally find it more comfortable?
  3. The link you sent is an April Fools joke lmao, even for C & C++ developers that syntax is horrible to look at lol.
  4. In my personal opinion, and based on experience working with LLVM: Sure, its the most popular option, and it is very powerful, but its not really stable. If you want stability and safety, the closest thing you have right now that it has made tons of progress is Bytecode Alliance’s Cranelift. Otherwise, i don’t recommend using LLVM as a backend unless you have no other choice, but its not the end of the world if you have to use it lol.

You obviously never had to use msvc6.

See above and now eyesight problems.

You’ll also find that some people come to Ada because they’re sick of c-likes.

You get stability to sticking with a major version series.

You obviously never had to use msvc6.

Yeah, never did… should i? lmao
I like Rust’s syntax formatting and identation more than C’s anyways.

See above and now eyesight problems.

…?

You get stability to sticking with a major version series.

That depends on the version you’re getting. Some are more or less stable, but LLVM’s goal from the beginning is to be a backend for experiments. Again, its not bad to use LLVM lmao, but you’re gonna find nasty bugs or undocumented stuff from time to time, even if you’re on stable. Maybe i’m wrong and its not that big of a deal lol

No, it would mess up formatting and you’d have to count end braces which wouldn’t be necessary with “end Some_Identifier”.

I meant the msvc comment.

I have to work in C/C++, Rust, JavaScript, and Python. Of those four, the most readable is Python, because indentation beats braces any day. I like a lot of things about Rust (starting with rust-analyzer’s vast superiority to just about anything I’ve used for C/C++) , but its decision to mimick C/C++’s terrible syntax is not one of them.

Whereas Ada’s begin / end <corresponding structure> is far clearer than Python. And it isn’t just Ada; it’s the Pascal family, Eiffel, and others. It’s inherently superior to { } because i can, at any moment, see precisely what is ending. Whereas it is often the case that I mislocate some } in Rust or (especially) C/C++ and not even the IDE can figure out what’s wrong, and I waste time (and increase frustration) trying to work that out.

My only complaint about begin / end in Ada is that i’d personally prefer end <Identifier> on every occasion where it applies, but you end record instead, and maybe some other things. But that’s just taste; it’s not an ambiguity I have to worry about.

2 Likes

I wish! I really liked Kotlin when I was using it.

Whereas Ada’s begin / end <corresponding structure> is far clearer than Python. And it isn’t just Ada; it’s the Pascal family, Eiffel, and others. It’s inherently superior to { } because i can, at any moment, see precisely what is ending. Whereas it is often the case that I mislocate some } in Rust or (especially) C/C++ and not even the IDE can figure out what’s wrong, and I waste time (and increase frustration) trying to work that out.

Hey! That’s actually true, now that i think of it!

My only complaint about begin / end in Ada is that i’d personally prefer end <Identifier> on every occasion where it applies, but you end record instead, and maybe some other things. But that’s just taste; it’s not an ambiguity I have to worry about.

That’s a really good idea tho, i could try to add that to the compiler, and see how it goes! :smiley:

I wasn’t sure which it was, that’s why I said “I think,” but it’s scala

1 Like

You might want to help with this: Ada Bootstrap compiler funding thanks to nlnet! - #51 by kevlar700

Hmm, even in Scala end seems to be optional, but perhaps I misread that.

Ok, so a bit of language-geeking here; the syntactic-notation is perhaps the least important aspect of the language: what is much more important is the semantics, and how the various components fit together. — To illustrate this point, consider a theoretical Ada compiler where there are no ‘files’, no ‘text’, but rather that everything is stored as structures in a database: with such a system, to edit a particular item you could (a) update the database directly, or (b) pull the structure into an editor, ‘regenerating’ the text, and then parsing and updating the database when finished. In option b, the textual representation for a construct could reconstruct keywords in a different language, such as begin/end pairs as 開始/終了 (Japanese).

However, the syntactic structures are important. Consider for a moment Ada’s if-then/end if construct:

If Condition then
   Statement;
End If;
if( condition )
  statement;

There are no parentheses in Ada because if+then delineates (ie parenthesizes) the condition, since C has no then it must use some other method to delimit the condition (the parentheses). Next, look at how adding Statement_2; in Ada is simply adding the statement, whereas in C you must now go in and add { & }, this is because the end if token parentheses the sequence-of-statements and there is no such delimiter in C. (Note, also, that the end if token solves the dangling-else problem, solving the grammar ambiguity.)

So, to put it a bit more gently that others would: if you want to do something like this, design your compiler as I outlined above and, after you have everything working, then make your C-like as an editor presentation.

See, here’s a really big problem, IMO:
Now it seems that many programmers are equating the program with the text, and that’s rather a step backwards. Your program is NOT unformatted text, there is structure to it. There’s a reason that text-based diff is far, far inferior to semantic-diff: tab vs space shouldn’t be a thing.

In Python, a mistake that I’ve made is having the wrong indentation for the last line of a loop, which changes the semantics of the program. The statement is either done in a loop when it should have been done once or vice-versa, and it is difficult to tell what was intended.

Most of my career, I programmed primarily in C or C++. When I discovered Rust, I found that it seemed to have almost everything I’d wished for in C++. Later, when I discovered Ada and as I learned more about it, I found that it additionally had things I didn’t know I wanted or needed. The design of the syntax to help to prevent common errors is one of the impressive things about Ada.

But being able to mark the end of a scope with information about which scope-beginning tag it was associated with was one of the things I’d wished for in C and C++ that Rust didn’t deliver. Ada comes adequately close to delivering what I’d wished for. I used to put comments after my closing braces like }// if to indicate which opening brace that closing brace was associated with. Sometimes I copied the whole line or a major part of the whole line starting the scope into the comment like } // if (0 == x) to distinguish between multiple statements, maybe nested.

One of the things that can happen is a mistake with cutting and pasting, and at some point, I decided that I’d like the compiler to be able to tell me when I’d made such a mistake. Of course, you might say that I shouldn’t be cutting and pasting, but eventually it becomes hard to avoid. And, of course, I know to do it with care, but I am human.

Here were some of the things I tried in C++ (According to my recollection, I didn’t use these professionally; I just experimented with them at home):

    if (const bool test_in_if_stmt_scope = true)
    do {
            ... ;
    } while (!test_in_if_stmt_scope); //do once

The point of that code was that the closing brace couldn’t be separated from the declaration of the variable in the if statement without resulting in an compile-time error.

I also found this code:

/*! class closing_brace_marker_type_
 * \brief type for marking a closing brace as distinct from other closing braces
 *
 * Use this class by creating an instance with a unique name at the beginning of a scope
 * Use the instance as the last program statement within the scope, right next
 * to the closing brace.
 *
 * If a closing brace intended for another scope is accidentally (e.g., by cut and paste)
 * placed in this scope, the compiler will complain the instance was not declared in scope.
 *
 * example:
 * while (!done) { closing_brace_marker_t closing_brace_marker_for_while_not_done_scope;
 * ... //program statements
 * closing_brace_marker_for_while_not_done_scope = 0}
 */
typedef class closing_brace_marker_type_ {
public:
    const closing_brace_marker_type_ &operator=(int a) { a=a; //a is a dummy value
                                                         return *this; }
    template <typename T>
    static const T &identity(const T& val) {return val;}
} closing_brace_marker_t;

The code is about a decade old, and the comment doesn’t explain what the identity function is for, but I found an example where I used it:

int num_shared_coreferences = [](int article_num, int claim_s_id, int evidence_s_id) { closing_brace_marker_t closing_brace_marker_for_num_shared_coreferences_lambda;
...
return closing_brace_marker_for_num_shared_coreferences_lambda.identity(temp); }(claim_article_num, claim_sentence_id, evidence_sentence_id); //end of lamda definition

Apparently, it was to be used when the last statement before a closing brace was a return statement. In this example, the braces are part of the scope for a particularly long lambda function definition, and the function is called immediately. That was a technique I used sometimes to ensure that my variables were assigned an appropriate value before they were used, and C++ doesn’t have another way to make nested or anonymous functions. A good alternative would have been a global function, but since C++ lambdas are not closures by default, I could later just cut and paste the contents of the lambda to a new global function assured that it was not dependent on any variables in the outer scope (because the brackets at the beginning are empty).

If you know C++, you might notice that I could have just used an int instead of a dedicated type and declared it at the beginning of the scope and assigned it at the end. As long as I was using such long names for the variables, there probably wouldn’t have been any naming conflicts. I suppose I wanted to be as explicit as possible about its purpose.

I also found comments like this in my old C++ code:

{ //BEGIN SCOPE FOR LOADING CACHED SIMILARITY SCORES
...
} //END SCOPE FOR LOADING CACHED SIMILARITY SCORES

To OneWingedShark’s point, the actual syntax used to start or end the scope is less important than the fact that it is made difficult to mix up scopes and that the scope one is looking for is easy to find.

Rust and/or C++ could extend their braces syntax to add labels at the beginning and after the end, but as it is, the braces syntax was not even providing the functionality that I wanted before I discovered Ada.

1 Like

I suspect someone might comment that I should have shorter blocks, and they would probably be right. It is just part of my workflow that I often, at least initially, end up with long blocks. Ideally, I would try to improve the code later.

Nifty Ada feature: named loops:

OUTER:
For X in Input'Range(1) loop
  -- Stuff #1.
  INNER:
  For Y in Input'Range(2) loop
    -- Stuff #2.
    -- Oh, we can exit named-loops
    Exit INNER when Input(X,Y) = 3;
    Exit OUTER when Input(X,Y) not in Positive;
  end INNER;
  -- Stuff #3.
end OUTER;

You probably knew it, but I figure it could be restated for those new to Ada.

4 Likes

Wow, I don’t think I’ve ever made that mistake. My condolences.

Same here. It seemed to help, but it doesn’t when you’re editing someone else’s code, since most of the time those someone elses won’t be bothered to do that.

It’s like one of my least favorite pieces of advice from C++ advocates: “-Wall is your friend.” Do such people ever compile against other people’s code?!? It seemsI often run across widely-used libraries that fire out warnings left and right.

I won’t say that; I do it whenever I decide to move code from one place to another, so that I can avoid duplicating it. I probably haven’t phrased it well: if I need exactly the same code in two places, I create a new, empty function, cut from the place that has the code, paste into the new function, and now I can call it from both places :grin:

Now, copy and paste: that’s a problem I deal with at work a lot. It causes a lot of confusion, especially if you find a bug in one location and fix it, but the bug persists because you haven’t fixed it in the other 10.

Those C++ tricks were pretty inventive. Did they work out well for you?