Type Extensions Across Modules

General discussions about working with the Astrobe IDE and programming ARM Cortex-M0, M3, M4 and M7 microcontrollers.
Post Reply
gray
Posts: 109
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Type Extensions Across Modules

Post by gray » Fri Apr 12, 2019 2:56 am

Let's assume we have this module as starting point:

Code: Select all

MODULE M1;

  TYPE
    T1* = POINTER TO T1Desc;
    T1Desc* = RECORD
      i: INTEGER
    END;
     
  PROCEDURE P1*(t: T1);
  END P1;
  
  PROCEDURE Init*(t: T1);
  BEGIN 
    t.i := 13
  END Init;
  
END M1.
Now I want to extend the type as follows:

Code: Select all

MODULE M2;

  IMPORT M1;
  
  TYPE
    T2* = POINTER TO T2Desc;
    T2Desc* = RECORD(M1.T1Desc)
      j: INTEGER
    END;
     
  PROCEDURE P2*(t: T2);
  BEGIN 
    t.i := 4 (*problem*)
  END P2;
  
  PROCEDURE Init*(t: T2);
  BEGIN
    t.j := 17
  END Init;

END M2.
The compiler flags the assigment in 'P2' as error (undef). Of course I can export 'i' in M1, but that would be contrary to the concepts of encapsulation andprotected modularity. Also, if I am not the author of M1, and/or don't have the source code, this is not even an option.

If I put all the code into one module...

Code: Select all

MODULE M3;

  TYPE
    T1* = POINTER TO T1Desc;
    T1Desc* = RECORD
      i: INTEGER
    END;
    
    T2* = POINTER TO T2Desc;
    T2Desc* = RECORD(T1Desc)
      j: INTEGER
    END;
    
  PROCEDURE P1*(t: T1);
  END P1;
  
  PROCEDURE P2*(t: T2);
  BEGIN 
    t.i := 4
  END P2;
  
  PROCEDURE InitT1*(t: T1);
  BEGIN 
    t.i := 13
  END InitT1;

  PROCEDURE InitT2*(t: T2);
  BEGIN 
    t.j := 17
  END InitT2;

END M3.
... the compiler happily accepts the assignment, ie. it "knows" that 'i' is an element of the extended type.

To me, 1) this does not seem consistent, and, 2) in the case of the separated modules, is a real limitation. The author of a module should not need to "prepare" possible future extensions, aside from the problems with exporting implementation details of a type.

Is this a limitation of the language or the compiler? Or do I miss something here?

(FWIW, the practical use case here is a serial USART module that I want to extend with a data transmission functionality using a buffer and interrupt-driven sending.)

cfbsoftware
Site Admin
Posts: 493
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Type Extensions Across Modules

Post by cfbsoftware » Fri Apr 12, 2019 9:41 am

If you want to access the field i directly outside M1 then you must mark it as exported. If you want to keep i hidden but change its value outside of M1 then you can implement an exported “setter” procedure in M1 e.g.

Code: Select all

(* untested *)
PROCEDURE SetHiddenField*(t: T1; val: INTEGER);
BEGIN
  t.i := val 
END SetHiddenField;

gray
Posts: 109
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Type Extensions Across Modules

Post by gray » Fri Apr 12, 2019 1:57 pm

Oh, that's a bit disappointing. In my book, being able to extend a base type without the need to change this base type scores high. Again, is this a limitation of the language or the compiler?

Here's a loosely related follow-up issue I ran into today:

Code: Select all

MODULE M1;

  TYPE
    T1 = POINTER TO T1Desc;
    T1Desc = RECORD
    END;

  VAR
    ta: ARRAY 4 OF T1;
    t: T1;
    
BEGIN
  CASE ta[0] OF (* 1: does not compile: invalid type *)
    T1:
  END;
  
  IF ta[0] IS T1 THEN END; (* 2: does not compile: compiler error; reg stack not empty *)
  
  t := ta[0];
  CASE t OF   (* 3: compiles *)
    T1:
  END

END M1.
I was baffled by these errors (cases 1 & 2), as the code seems innocuous and straightforward. Any advice here, apart from using workaround 3?

cfbsoftware
Site Admin
Posts: 493
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Type Extensions Across Modules

Post by cfbsoftware » Sat Apr 13, 2019 4:14 am

gray wrote:Oh, that's a bit disappointing. In my book, being able to extend a base type without the need to change this base type scores high. Again, is this a limitation of the language or the compiler?
There appears to be some misunderstanding. You can certainly extend a base type without the need to change the base type. What you can't do is change the interface of a module from within an importing module.

A programmer is able to extend any type exported from a module with or without access to the source code. Without the source code of M1 you would have no knowledge of the existence of i. If the author of M1 determines that T1.i is private then he can make any change he likes to T1 (remove i, change the type of i etc.) without fear of invalidating any extensions to T1. I wouldn't want it to be any other way and am having difficulty understanding why you would. Do you have a real-world example to illustrate what it is you are trying to do?

At first sight the second part of your message appears to be a bug. I've relocated it to the 'Bug Reports' topic.

gray
Posts: 109
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Type Extensions Across Modules

Post by gray » Tue Apr 16, 2019 12:35 am

cfbsoftware wrote:A programmer is able to extend any type exported from a module with or without access to the source code. Without the source code of M1 you would have no knowledge of the existence of i. If the author of M1 determines that T1.i is private then he can make any change he likes to T1 (remove i, change the type of i etc.) without fear of invalidating any extensions to T1. I wouldn't want it to be any other way and am having difficulty understanding why you would. Do you have a real-world example to illustrate what it is you are trying to do?
Indeed, I understand your reasoning and the benefits of this strict separation. My points and arguments were not even consistent, I guess...

My real-world application is a USART device driver (Serials.mod), with an extension for interrupt-driven transmission based on a circular buffer (SerialsB.mod), where the CR1 and DR registers' addresses stored in the base type are also needed in the extended type.

Code: Select all

MODULE Serials;

  TYPE
    Serial* = RECORD
      ...
      DR : INTEGER;
      CR1: INTEGER;
      ...
    END;

  PROCEDURE* configUSART(s: Serial);
  BEGIN
    SYSTEM.PUT(s.CR2, {});
    SYSTEM.PUT(s.CR3, {});
    SYSTEM.PUT(s.CR1, {UE, RE, TE})
  END configUSART;

END Serials.

Code: Select all

MODULE SerialsB;

  TYPE
    SerialB* = RECORD(Serials.Serial)
      ...
      DRb: INTEGER;
      CR1b: INTEGER;
      ...
    END;
    
  PROCEDURE* configUSART(s: SerialB);
    VAR
      x: SET;
  BEGIN
    SYSTEM.GET(s.CR1b, x);
    SYSTEM.PUT(s.CR1b, x + {TXEIE})
  END configUSART;

END SerialsB.
As I see it, there are two possibilities: 1) export the data from the base type, or 2) data redundancy. I currently use redundancy, it's only two INTEGERs (DRb is used by the interrupt handler to transmit the data, not shown here).
But maybe I miss another alternative approach?

cfbsoftware
Site Admin
Posts: 493
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Type Extensions Across Modules

Post by cfbsoftware » Sun Apr 21, 2019 12:12 am

Serials.configUSART would be more appropriately named Serials.EnableUSART. This should be separated from the code that just configures the registers. You could then include one or more additional exported procedures in your base module that allowed configuration of the registers (e.g. add / remove bits like TXEIE) without having to know the names / addresses of the registers in the base module.

Post Reply