Vjalmr In Wonderland - Everything is C Down the Rabbit Hole?

So, as I am learning Ada, I started doing some digging.
I wanted to see if I couldn’t write some kind simple program with a terminal user interface.

While searching for this, I came across the binding to curses/ncurses, but I thought, hey, why use a C library for this, when I want it to be Ada?

So I look up the following article on terminal control, and it gives a lot of useful information. A lot of it can actually be done with Ada, until…

I then attempted to implement some of this, but again hit a wall when I reached ioctls and termios, which are all C as well.

Hitting my head against the keyboard for a while, the task being way over my head, trying to figure out how these worked, so I could possibly implement a method of doing it with Ada.

This lead me to a-textio.adb, and eventually some documentation (that I am sorry to say, I forgot to bookmark, but it tells me what it in the source) somewhere that skewed my perspective a bit. It just uses a C interface.

So I can gather that using C is probably the easiest thing to do, seeing how (for me it’s a Linux system) the system one is working on is probably written in C, so most calls would interop with it… But, isn’t it a bit disappointing? I was for some reason, possibly naively, hoping that this could have been done in Ada.

One of the things I have noticed going in to Ada, reading forums and articles, is that for more or less any problem, the answer is “use a binding to some C library”. Doesn’t this somehow take away from the so called safety of using Ada?

My question now is, is it all just a fancy wrapper around C, with hopes that whomever wrote the compiler did their job right when creating the bindings?

As long as your kernel is written in C, you’re going to need to use its calling convention, no matter what language you’re using.

There are a few kernels written in Ada, but I don’t think you’ll find anything nearly as full featured as Linux. These are mostly useful for embedded systems.

As for safety, that’s a bit of a loaded term that means a lot of different things. If you’re referring to memory safety, then yes, C bindings need to be specified with correct types and you’re relying on the C code to not do silly things with pointers.

If you turn on SPARK_Mode and run gnatprove in any mode, it should make it painfully obvious when you’re doing something potentially harmful on the Ada side of things.

Most terminal drawing can be done by printing ANSI escape codes. You can emit these codes directly or use a library. There are a few, but I like GitHub - mosteo/ansi-ada: ANSI control sequences for the Ada language

If you wanna get really fancy, I’ve got a binding to notcurses, but it’s incomplete and not very well documented.

1 Like

This is the GNAT implementation. There is no requirement that it be done that way. But ultimately I/O is going to involve an OS call …

I agree that binding to C is adding others’ errors to your S/W, and prefer all-Ada solutions, such as Ada GUI or GID. But obviously many people disagree. In many cases, until people create equivalent functionality in Ada, there’s no other alternative.

But one can accomplish quite a lot without any bindings; I haven’t used a binding in years. So no, it isn’t all “a fancy wrapper around C”.

In that case all you probably need are ANSI escape sequences, as in PragmARC.Ansi_Tty_Control.

1 Like

I wrote trendy terminal to do cross-platform VT100 stuff and terminal line editing/completion on Windows/Mac/Linux. You can see example usage in Septum.

My methods could be completely wrong, I have no professional Ada experience, I figured how out to do it from docs and other Alire projects (SDLada, iirc). All my C bindings are written by hand from header files – GCC can dump bindings but I’ve always just only bound what I need.

My procedure for doing this looks something like:

  • Find the right headers
  • Port over a small subset of functions and types (only 1 if possible), just to ensure any libraries used link in the build.
  • Start porting over the other C types and functions I need directly.
  • Fix up macros from C code, they’re always a huge pain to deal with for me, structs specifically.
  • Adjust C code to use Ada elements which help with meaning – bit flags becoming bit arrays, appropriately set up enums, etc.
  • Get one platform working
  • Hide the working platform details behind a package interface.
  • Set up GPR to select a different implementation based on platform.
1 Like

I have plenty of experience with this kind of thing. Here’s a fun fact: VMS, from what I’ve read, had a real calling convention specified, which every language could use; UNIX, of course, did away with all that complicated stuff and replaced it with something much more complicated.

Anyway, I ran into this kind of nonsense when I was writing my UDP library. I wrote it with no C language code whatsoever:
http://verisimilitudes.net/2022-04-05

I then use that diseased package to write the interface I prefer:
http://verisimilitudes.net/2022-08-22

As for Curses and Ncurses, they’re garbage, don’t use them. I read the ECMA-48 Standard, and some related documents, and wrote a library entirely avoiding the C language in Common Lisp:
http://verisimilitudes.net/2017-12-31
http://verisimilitudes.net/2018-04-04

The first library implements every ECMA-48 function in an optimized way, and the second composes them into something useful. This Common Lisp no longer meets my very high standards, at least in terms of formatting, but it may be suitable for study nonetheless.

Yes, it does, which is why it shouldn’t be done.

No. On other systems, Ada is not wed to some monster like this. At the level of the system call, someone does just have to get it right and, since Ada 1995 has a standard package for interfacing with the C language, it’s not entirely unreasonable for compiler authors to basically use that package for interfacing with the underlying system, but these implementation details don’t impact the safety of Ada, unless they get it wrong.

You can write C code calling C functions and deal with all the problems of C: both your oversights and someone else’s.

Or, you can write Ada code calling C functions and deal only with someone else’s oversights.

1 Like

This surprised me when I started looking into the implementation of the standard library too; It’s one of the reasons why glibc is a dependency, I think.

I wonder how some other languages like rust managed to implement their standard library without using any c… or do they? The effort to rewrite coreutulils (which I would love to see in Ada) comes to mind.

And argument could be made that it is not as fully featured as Linux, but Ironclad is a fully posix-compatible monolithic desktop kernel written in Ada: https://ironclad.nongnu.org/

There’s even a distribution for it with the widows manager and everything.

2 Likes

Thank you all for your very informative responses. It has given me things to think about.

This is the same answer, more or less, that I got when learning a bit of Common Lisp.
I understand that I will have to use the system’s call structure, but I believe my greatest hurdle is accepting that fact.

Again, as always, the Ada community is always very patient, friendly, and helpful. Thank you.

FPC (and apparently rust) use the syscalls directly, no intermediate lib, on Linux anyway. Don’t know if they do the same on BSD and you can’t do that on windows.

1 Like

The biggest problem I saw with some Common Lisp programmers was their unwillingness to actually learn the language. I saw Common Lisp programmers who reached for a C language routine because they didn’t know the corresponding Common Lisp functions. Understand that linking to a C language routine is an action of last resort. Only do it when there’s absolutely no option. In most cases, lazy programmers just want to reuse broken libraries, and then their programs are flawed in the same way the C language programs are.