String Literal Parameter

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

String Literal Parameter

Post by 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)?

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

Re: String Literal Parameter

Post by 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).

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

Re: String Literal Parameter

Post by 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);

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

Re: String Literal Parameter

Post by 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?

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

Re: String Literal Parameter

Post by 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.

Post Reply