The Imperium Programming Language - IPL

nsaspook

Joined Aug 27, 2009
13,265
I don't think you've ever answered my question - what exactly do you think would be useful, helpful features above and beyond what C provides, in the context of writing code for MCUs?

Do you think:

1. There are no such features possible, C cannot be improved upon.
2. I don't care, C is sufficient for me and I do not care if it can or cannot be improved upon.
3. Yes I can think of the following that I would like to see or be able to express in C:
...
IMO You're getting side-tracked there with that sort of pigeon-holing. Not in a bad way, as we all have our biases.

The Spirit of C includes the principles "Trust the programmer" and "Don't prevent the programmer from doing what needs to be done". If your new language sticks to that it has a chance.

It's not features, it's value. Can that feature provide value to the engineering process of a final product. That's how vendors sell products. It's immensely valuable to stick to 8->32-bit controller compatibility solutions in C99 or C11 for embedded programming today instead of C89 because of features, so I care about valuable features. Vendors like Bosch Sensortec invest in providing compatible C solutions to sell their products. Almost every micro-controller vendor on the planet provides valuable C compatible solutions.

I'm not a C loving guy as I would prefer a more Wirth language like a modern Module-2. I'm not a C language fanboy, I'm a hardware junkie that uses a C needle.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
IMO You're getting side-tracked there with that sort of pigeon-holing. Not in a bad way, as we all have our biases.

The Spirit of C includes the principles "Trust the programmer" and "Don't prevent the programmer from doing what needs to be done". If your new language sticks to that it has a chance.

It's not features, it's value. Can that feature provide value to the engineering process of a final product. That's how vendors sell products. It's immensely valuable to stick to 8->32-bit controller compatibility solutions in C99 or C11 for embedded programming today instead of C89 because of features, so I care about valuable features. Vendors like Bosch Sensortec invest in providing compatible C solutions to sell their products. Almost every micro-controller vendor on the planet provides valuable C compatible solutions.

I'm not a C loving guy as I would prefer a more Wirth language like a modern Module-2.
OK fair enough. I don't have any aspirations that the language will become popular and start to replace C in the embedded world. There are quite a few languages playing in that field right now, some have lots to offer but they also carry their own baggage too.

The goal is really to create a first class "systems programming" language, a non-OO language, an imperative language, should it gain any kind of following then fine, good but I don't expect that to just happen, just because.

For example the type "offset" is a type just as "pointer" is a type, but an offset is a relative pointer - no real support for that in C and in C++ one has to jump through hoops to "simulate" it.

So structures can contain "offset" members rather than "pointer" members, allowing linked data structures to be copied/moved much more quickly than when absolute pointers are used. The same notation is used too, you can reference a datum via a pointer or an offset using the familiar -> symbol, this isn't my idea either, PL/I had this and more.
 

joeyd999

Joined Jun 6, 2011
5,283
@nsaspook, not that I want to be drawn into this laborious thread, but one thing I find seriously lacking in xc8 is the inability to link resource files, where constants (especially, strings and blobs) can be pulled in to the code at link time.

Such a feature would be invaluable for customizing firmware for different customers, and would make internationalization of applications far easier.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
Well I pinned down the grammar for condition expressions, these are examples of what one can write:

Code:
a = (x,y) ↦ ((1,2),10:"A")(3,20:"B")("*");  // can also use --> instead of ↦
That literally means:

Code:
if (x = 1 || x = 2) && y = 10 then
   return "A";
elif x = 3 && y = 20 then
   return "B";
else
   return "*";
end;
Of course that "if" code would also need to be inside a function in order to be used as an expression. This notation is very compact, more compact than even some functional languages and much more compact than C or C++ or C# and Java.

Furthermore there's no restriction that the values being tested for (1,2,10, 3 etc) must be constant, they are in that example but can themselves be expressions with values known only at runtime.
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,265
@nsaspook, not that I want to be drawn into this laborious thread, but one thing I find seriously lacking in xc8 is the inability to link resource files, where constants (especially, strings and blobs) can be pulled in to the code at link time.

Such a feature would be invaluable for customizing firmware for different customers, and would make internationalization of applications far easier.
What exactly do you mean by a resource file? Icons, menus, dialog boxes, strings tables, user-defined binary data?
I normally do that in the preprocessor #include stage with .h files and the linker.
https://microchipdeveloper.com/mplabx:projects-loadable
https://microchipdeveloper.com/xc8:customizing-user-sections
project properties -> XC8 global options -> XC8 linker, changing the Option categories: to Additional options, and entering the offset in the Codeoffset field:
https://microchipdeveloper.com/faq:12
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,265
Well I pinned down the grammar for condition expressions, these are examples of what one can write:

Code:
a = (x,y) ↦ ((1,2),10:"A")(3,20:"B")("*");  // can also use --> instead of ↦
That literally means:

Code:
if (x = 1 || x = 2) && y = 10 then
   return "A";
elif x = 3 && y = 20 then
   return "B";
else
   return "*";
end;
Of course that "if" code would also need to be inside a function in order to be used as an expression. This notation is very compact, more compact than even some functional languages and much more compact than C or C++ or C# and Java.

Furthermore there's no restriction that the values being tested for (1,2,10, 3 etc) must be constant, they are in that example but can themselves be expressions with values known only at runtime.
Your BNF is starting to make a syntax that looks like Forth. That brings back good and bad memories. https://forum.allaboutcircuits.com/...ms-that-are-still-running.132038/post-1092256

"My favorite programming language is solder"
-- B. Pease & S. Ciarcia
 
Last edited:

camerart

Joined Feb 25, 2013
3,730
Hi,
I use BASIC, but that's because I'm a poor learner.

I heard Elon Musk say when he was talking about programming his rockets, the he uses C++, but in some cases C is better. I didn't understand why?
C
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
Hi,
I use BASIC, but that's because I'm a poor learner.

I heard Elon Musk say when he was talking about programming his rockets, the he uses C++, but in some cases C is better. I didn't understand why?
C
Why? Elon Musk frequently doesn't know what he's talking about is the likely answer!
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
How about macros without side effects? That would certainly be a nice feature.
Yes a preprocessor capability is on the cards, in some form. I'm happy to discuss this here, perhaps identify concrete requirements and take it from there.

Zig uses the concept of code tagged as "comptime" which is how they achieve some compile time stuff.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
Many here have made the case that an attraction of C is the degree of "control" it gives a developer, yes that carries risks but the trade off is felt to be worth it.

So on that note I've now started to look at things that are much closer to the "metal" as it were.

One thing that seems to be obvious is the ability to initialize the stack pointer register, there are many examples of MCU bootup code that must do this yet the C language doesn't expose it and in many cases it is done using embedded assembler, the very thing a compiler should be able to routinely generate.

So I looked at an STM32F446RE C project here and see that the stack is set in two ways, one is by populating the start of RAM with a predefined vector table, the first address being the SP base address that's defined in the linker script.

The other being conditional compilation, where we use embedded assembler to set SP to the address of an external variable (again, the same name that has the stack address assigned in linker script).

That latter course of action takes place in a reset handler (also at an address defined in the vector table) that is annotated as "naked" and "noreturn" using the non standard catch all __attribute__ whose allowable options varies by compiler vendor.

So these are the kinds of things I want to call out, identify and incorporate as an innate part of IPL, not leave them as implementation defined or as stuff that need embedded assembler.

A systems programming language, the one I envision, should do these kinds of things routinely, they should be fully supported and defined by the language standard, I want it to provide the power and control spoken of in this and other threads here.

So, thoughts...
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
OK after more investigation I am now thinking that the language can leverage the same names used by C and the many optional attributes. Also there's a good argument to be made for linguistically supporting a block concept for attributes so that all declarations within the block become attributed, no need to specify the attribute on ever single function.

Since IPL has a very extensible grammar thus can be added easily (in fact if the language had already been released and was in use by people, adding this would be a non-breaking change)

Code:
attributes declare(section(".boot"),used) procedure(naked, noreturn, section(".boot"));

   // every proc defined in here will "inherit" the "procedure" attributes.

   // every declaration (not local to a procedure, not stack) will inherit the "declare" attributes

end;
This seems to be a natural thing to consider given the overall nature of these __attributes__. These attributes can of course be applied individually still and additional attributes applied to certain members in the block get added to the block's attribute or can override a block's attributes if desired.

This is all emerging as a direct result of considering the features desirable for a serious systems programming language.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
Well I've updated the grammar to now support this (that is to say, it can now be parsed correctly). This is more or less a place-holder at this stage because the low level specific are yet to be designed but the syntax provide a means of doing this now.

I called these things "traits" (because the language grammar itself already uses the term "attributes" and would confuse).

These traits can be specified of course, on any individual func/proc but can also be used to "wrap" a bunch of funcs/procs like the numerous default handlers we see on an STM32 device.

Code:
scope system.vectors;

    traits proc(naked, section(".vectors"));

      proc Reset_Handler;

        // copy ISR handler addresses from FLASH to RAM

      end;

      procedure Default_Handler;

        loop; // infinite loop

        end;

      end;

    end traits;

end scope;
Like many other concepts in the language, traits are a block and so have a keyword/end structure. Traits can be contained within scopes (aka namespaces) and traits can only contain declarative statements like declarations and proc definitions etc). The ends can have an optional keyword to match the statement too, this is entirely optional.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
To support the embedding of assembly code in certain places (actually this will only be possible inside a special type of procedure known as an "intrinsic" and is the way that true compiler supplied intrinsics will be written too) a new element was added to the grammar, a "directive" in this case named "accept" meaning accept text in whatever language (e.g. assembler).

Code:
procedure low_level_stuff intrinsic(arm) ;

    #accept(assembler)
        LDR R1, =#0x20001000    /* Load address 0x20001000 to R1 */
        LDR R2, =#0x20001004    /* Load address 0x20001004 to R2 */
        LDR R3, =#0x20001008    /* Load address 0x20001008 to R3 */
        LDR R0, [R1]            /* Load data at the address pointing by R1, save to R0 */
        LDR R1, [R2]            /* Load data at the address pointing by R2, save to R1 */
        ADD R0, R1              /* Add R0 to R1, save to R0 */
        STR R0, [R3]            /* Store R0 to the address pointing by R3 */
    #end

end;
So this is how all intrinsics will be coded and all inline assembler can be represented this way, there is no call/return semantics here because an intrinsic procedure (or function) is not a "real" procedure or function but looks like one.

The lexical analysis inside the #accept block is also specific to assembler, so we do not need to embed the assembly code inside strings like we do in C and C++ etc.

Instead syntax coloring will be possible for embedded assembler. The target is known from the intrinsic keyword's argument on the procedure statement. This model supports other languages too so embedding SQL or whatever can be done with this general pattern I think.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,609
I've revised this after re-examining it over the past 24 hrs. This is now the IPL way to implement embedded assembler:

Code:
procedure breakpoint(X) intrinsic(arm) ; 
        /* embed these hard coded values, they */
        /* implement simple jump table   */
   
        0x123
        0X123
        0111b
        0123h
        023H
        0o1256
        01123d
        11001101010100b
        10101010101010y
        LDR R1, =#0x20001000    /* Load address 0x20001000 to R1 */
        LDR R2, =#0h20001004    /* Load address 0x20001004 to R2 */
        LDR R3, =#0o20001008    /* Load address 0x20001008 to R3 */
        LDR R0, [R1]            /* Load data at the address pointing by R1, save to R0 */
        LDR R1, [R2]            /* Load data at the address pointing by R2, save to R1 */
        ADD R0, R1              /* Add R0 to R1, save to R0 */
        STR R0, [R3]            /* Store R0 to the address pointing by R3 */
end;
Of course that actual assembler code is junk, just copy/pasted stuff to test the parser. In IPL embedded assembler is always placed inside an intrinsic procedure block, a procedure that has the intrinsic attribute. To actually embed the code inside other code one just references the procedure (or function) and the compiler performs an inline insertion of the intrinsic code.

Code:
...
insert breakpoint(25);  // unlike ordinary procedures, intrinsics require the "insert" keyword.
...
This identical pattern is used to also create true intrinsics that are often perceived as part of a language, its a neat pattern. Now several people here emphasized "control" when describing what C provides, well I was listening, you want control? you'll get control.

Just to be clear too, the IPL parser changes mode when it enters an intrinsic procedure's body, the statements in there are not IPL statements, they are assembler and the syntax is parsed as assembler and that syntax is very different to IPL's own syntax, which resumes once the parser reaches the end of the intrinsic procedure block.

The above code fragment is taken from a larger test source file, you can see that here to get a more rounded idea of what the language is starting to look like.
 
Last edited:
Top