String Literal Parameter

General discussions about working with the Astrobe IDE and programming ARM Cortex-M3, M4 and M7 microcontrollers.

String Literal Parameter

Postby gray » Fri Jun 14, 2019 12:21 pm

I don't quite understand why I cannot pass a string literal in this case:
Code: Select all
MODULE M;
  TYPE
    Pid = ARRAY 4 OF CHAR;
  VAR
    id: Pid;
    id2: ARRAY 4 OF CHAR;
   
  PROCEDURE P(pid: Pid);
  END P;
 
BEGIN
  P("id"); (* error *)
  id := "id";
  P(id); (* ok *)
  id2 := "id";
  P(id2) (* ok *)
END M.

Isn't "id" a string, which by itself is an expression, and the formal parameter stands for its value (ActualParameters => ExpList => expression => SimpleExpression => term => factor => string)?
gray
 
Posts: 51
Joined: Tue Feb 12, 2019 2:59 am

Re: String Literal Parameter

Postby cfbsoftware » Fri Jun 14, 2019 1:19 pm

Good question. It appears that a string literal is not considered to be an array in this context. This is even more evident if you try the statement:

Code: Select all
i := LEN("id");

An exception is that you can pass a string literal when the parameter is an open array. That is normally how literal strings are passed as procedure parameters:

Code: Select all
PROCEDURE P(pid: ARRAY OF CHAR);
END P;

However, as shown in your example, strings of varying length can be assigned to variables that are arrays of characters. This is allowed as an exceptional case that is specifically stated in the language report:

9.1. Assignments

2. Strings can be assigned to any array of characters, provided the number of characters in the
string is less than that of the array. (A null character is appended).
cfbsoftware
Site Admin
 
Posts: 395
Joined: Fri Dec 31, 2010 12:30 pm

Re: String Literal Parameter

Postby gray » Sat Jun 15, 2019 4:16 am

Indeed, using an open array is the work-around, combined with an ASSERT to check the length.

The API just gets less expressive and "compile-time-checkable" this way:
Code: Select all
  TYPE
    ProcessID* = ARRAY 4 OF CHAR;

  PROCEDURE Init*(p: Process; proc: Coroutines.PROC; stack: ARRAY OF BYTE; startAfter: INTEGER; period: INTEGER; pid: ProcessID);

vs.
Code: Select all
  PROCEDURE Init*(p: Process; proc: Coroutines.PROC; stack: ARRAY OF BYTE; startAfter: INTEGER; period: INTEGER; pid: ARRAY OF CHAR);
gray
 
Posts: 51
Joined: Tue Feb 12, 2019 2:59 am

Re: String Literal Parameter

Postby cfbsoftware » Sat Jun 15, 2019 5:13 am

I agree that is getting to be unwieldy. If all or some of those parameters are closely-related I recommend grouping them into a record type. Also, is there a good reason why ProcessID is a 3-character (+null) string and not an integer named constant?
cfbsoftware
Site Admin
 
Posts: 395
Joined: Fri Dec 31, 2010 12:30 pm

Re: String Literal Parameter

Postby gray » Sun Jun 16, 2019 8:32 am

I agree, the parameter list for this procedure is on the edge, but still just acceptable. But defining a record type for just some config parameters is worth a thought, especially as I already factored out one parameter, assuming a default value (ptype := Essential), and providing an additional config procedure for changing it if required, which is only needed in a minority of cases.

Here's a comparison of the approaches:
Code: Select all
  TYPE
    ProcessID* = ARRAY 4 OF CHAR;
    Ticks* = INTEGER;

    Process* = POINTER TO ProcessDesc;
    ProcessDesc* = RECORD
      cor: Coroutines.Coroutine;
      schedulerState: INTEGER;
      next, nextR: Process;
      period, ticker, delay: Ticks;
      ptype: INTEGER;
      resetCount: INTEGER;
      id: ProcessID
    END;

    ProcessConfig* = RECORD
      startAfter*, period*: Ticks;
      ptype*: INTEGER;
      id*: ProcessID
    END;

  PROCEDURE Init*(p: Process; proc: Coroutines.PROC; stack: ARRAY OF BYTE; startAfter, period: Ticks; pid: ARRAY OF CHAR);
  BEGIN
    ASSERT(LEN(pid) <= 4, Error.PreCond);
    NEW(p.cor); Coroutines.Init(p.cor, proc, stack, pid);
    p.period := period;
    p.ticker := startAfter;
    p.id := pid;
    p.ptype := Essential;
    p.schedulerState := Off;
    p.resetCount := 0
  END Init;

  PROCEDURE Init2*(p: Process; proc: Coroutines.PROC; stack: ARRAY OF BYTE; cfg: ProcessConfig);
  BEGIN
    NEW(p.cor); Coroutines.Init(p.cor, proc, stack, cfg.id);
    p.period := cfg.period;
    p.ticker := cfg.startAfter;
    p.ptype := cfg.ptype;
    p.id := cfg.id;
    p.schedulerState := Off;
    p.resetCount := 0
  END Init2;

Regarding the type of ProcessID: I have considered using integers, but right now, the ID is only used in diagnostic messages and logs, and identifying a process by a mnemonic is easier than a number. I even pass the ID to the corresponding Coroutine for dev purposes for now, which I will drop later. Each process of the application program is implemented in a separate module, and "naming" the process is done there, ie not "centrally". Keeping the char-typed IDs apart in this scheme is easier than using integers, such as "hb" for the heartbeat process, "dp" for the display process, and so on. The role and use of the process ID might evolve, though, at which point I'll need to reconsider. Which is why having "pid: ProcessID" would have been preferrable over "pid: ARRAY OF CHAR". Your suggestion of using a config-record would in fact also account for that aspect.
gray
 
Posts: 51
Joined: Tue Feb 12, 2019 2:59 am


Return to Astrobe for ARM Cortex-M3, M4 and M7

cron