Conditional strings in plsql like sprintf - plsql

Writing the below code gives me error saying:
PL/SQL: Statement ignored
PLS-00382: expression is of wrong type:
Code:
if ( l_vol = 0 )
then
l_cndtn_string := 'l_wgt > l_wgt_limit';
else
l_cndtn_string := '(l_wgt > l_wgt_limit) and (l_vol > l_vol_limit)';
end if;
if ( l_cndtn_string )
then
l_isis_task := 'PO';
else
l_isis_task := 'TO';
end if;

If statement evaluates a boolean expression. In your example
if ( l_cndtn_string )
l_cndtn_string is not a boolean expression but a character expression and there is no implicit cast between them.
Please help yourself and check Expressions too.
I don't know what is your logic but the following example shows you one way to convert character expression into boolean expression:
if l_cndtn_string is not null -- a boolean expression
then
null;
else
null;
end if;

You want dynamic condition build. In plsql,like other compiled languages, it hard to do.
Try like this:
if ( l_vol = 0 )
then
if(l_wgt > l_wgt_limit) then
l_isis_task := 'PO';
end if;
else
if(l_wgt > l_wgt_limit and l_vol > l_vol_limit) then
l_isis_task := 'TO';
end if;
end if;

After IF boolean expression is expected, not a string - that's why you're getting PLS-00382 error. You can of course try to dynamically evaluate your expression using dynamic sql, but in fact what you want is as simple as:
if ( l_wgt > l_wgt_limit and ( l_vol = 0 or l_vol > l_vol_limit ) )
then
l_isis_task := 'PO';
else
l_isis_task := 'TO';
end if;

Oh, only after reading the other answers I realised what this question was about. I decided to keep also my original answer as the point about character expression in if-statement is still correct.
The other answers are correct that buiding a logic based on string evaluation with dynamic PL/SQL is not a good idea in general. They also present correct solutions, but IMHO even better approach exists.
Usually when I have multiple conditions in PL/SQL I give the conditions a name. Please see the example below that illustrates the technique. The names make the code self-documenting and is a huge improvement in code readability as very often the condition now reads as human language.
declare
v_volume number := 0;
v_weight number := 1;
v_weight_limit constant number := 10;
v_volume_limit constant number := 10;
v_has_volume constant boolean := v_volume > 0;
v_exceed_weight_limit constant boolean := v_weight > v_weight_limit;
v_exceed_volume_limit constant boolean := v_volume > v_volume_limit;
begin
-- no guarantee the logic is the same than in question
-- but just illustrates the coding style
if v_has_volume
and v_exceed_weight_limit
and v_exceed_volume_limit
then
null; -- something
else
null; -- something else
end if;
end;
/

Related

Using multidimensional array in Ada

In this code, I need help writing a multidimensional array with a range between 2020-01-01 to 2119-12-31.
My code works but as you see there are no arrays in it. How can I write this code with only arrays?
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
Procedure Date is
type date_type is record
Str : string (1..8);
Length : Natural := 0; end record;
A: date_type;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Get_Line (A.Str, A.Length);
exit when A.Length = 8;
Put_Line ("Wrong input. Try again.");
end loop;
Put_Line (A.Str (1 .. 4) & "-" & A.Str (5 .. 6) & "-" & A.Str (7 .. 8));
end Date;
Perhaps, rather than a multi-dimensional array you should consider using a record such as
type Year_Number is range 1900..3000;
type Month_Number is range 1..12;
type Day_Number is range 1..31;
type Date_Rec is record
Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
end record;
subtype Year_String is string (1..4);
subtype Month_String is string (1..2);
subtype Day_String is string (1..2);
function To_Date (Yr : Year_String; Mnth : Month_String; Dy : Day_String)
return Date_Rec is
Result : Date_Rec;
begin
Result.Year := Year_Number'Value (Yr);
Result.Month := Month_Number'Value (Mnth);
Result.Day := Day_Number'Value (Dy);
return Result;
end To_Date;
You can now pass around instances of Date_
Rec doing whatever you want with the date.
If you go this far then you might want to consider using the Time type described in Ada Language Reference Manual sections 9.6 and 9.6.1.
You haven't asked a reasonable question here because "I want to use arrays" is not a good reason to use an array.
"This problem can be best solved with an array ... but how do I deal with ... ?" would be a reasonable question, but you haven't stated a problem,let alone one that needs an array.
This is important because "using an array" is thinking in the solution domain, like "using a chisel". It's not the way to think about programming in Ada, (or IMO in any language).
Try thinking in the problem domain first : instead of "I want to use a chisel", I think "I want to recess this hinge so the door fits precisely" and a chisel is the neatest way of doing the job.
Then "How can I best validate a date?" would be one reasonable question, or "how can I store events that happen on each day for 100 years?"
The answer to the first question is probably in the Ada.Calendar package. Possibly the Value function in Ada.Calendar.Formatting, with an exception handler to catch incomprehensible strings and make the user try again.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar;
with Ada.Calendar.Formatting;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A); -- validate it's a date
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120; -- validate correct range
exception
when Constraint_Error => Done := False; -- Formatting.Value failed
end;
exit when Done;
Put("Try Again : ");
end loop;
end date;
The answer to the second is probably a 1-dimensional array indexed by Day_Count from Ada.Calendar.Arithmetic but let's use the wrong tool : a 3D array indexed by your range of years, Month_Number and Day_Number from the Ada.Calendar base package.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Calendar.Formatting;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean := TRUE;
Event_Array : array(2020 .. 2119,
Ada.Calendar.Month_Number,
Ada.Calendar.Day_Number) of Unbounded_String;
begin
Event_Array := (others => (others => (others => Null_Unbounded_String)));
Event_Array(2020,11,3) := To_Unbounded_String("nothing much");
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A);
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120;
exception
when Constraint_Error => Done := False;
end;
exit when Done;
Put("Try Again : ");
end loop;
Put_Line("Today " & Ada.Calendar.Formatting.Image(Date) & " : " &
To_String(Event_Array(Year(Date), Month(Date), Day(Date))) & " happened");
end date;
Test with the string 2020-11-03

Ada error: invalid use of subtype mark in expression or call

I'm stuck here with an error in my Ada program. There is a lot of code and I don't want to copy all of it here, so I hope that the part that I'm sharing is the part from where the problem comes.
task type Producent is
entry Start(Jedzenie: in Typ_Jedzenia; Czas_Produkcji: in Integer);
end Producent;
task type Buffer is
entry Zamow(Jedzenie: in Typ_Jedzenia; Numer: in Integer; Czy_Zatwierdzono: out Boolean);
entry Dostarcz(Zamowienie: in Typ_Zestawu; Numer: out Integer);
end Buffer;
task body Producent is
package Losowa_Produkcja is new
Ada.Numerics.Discrete_Random(Zakres_Czasu_Produkcji);
Generator: Losowa_Produkcja.Generator;
Index_Jedzenia: Integer;
Nr_Produkcji_Jedzenia: Integer := 1;
Produkcja: Integer;
Zatwierdzono: Boolean := False;
begin
accept Start (Jedzenie : in Typ_Jedzenia; Czas_Produkcji : in Integer) do
Losowa_Produkcja.Reset(Generator);
Index_Jedzenia := Jedzenie;
Produkcja := Czas_Produkcji;
end Start;
loop
delay Duration(Losowa_Produkcja.Random(Generator));
Put_Line("Przygotowano " & Nazwa_Jedzenia(Index_Jedzenia) & " numer " & Integer'Image(Nr_Produkcji_Jedzenia));
loop
Buffer.Zamow(Index_Jedzenia, Nr_Produkcji_Jedzenia, Zatwierdzono); <-------- ERROR
if Zatwierdzono = False then
Put_Line("Brak miejsca w kuchni dla " & Nazwa_Jedzenia(Index_Jedzenia) & ". Wstrzymanie");
delay Duration(3.0);
else
Nr_Produkcji_Jedzenia := Nr_Produkcji_Jedzenia + 1;
end if;
exit;
end loop;
end loop;
end Producent;
task body Buffer is
begin
Put_Line("Jestesmy u Buffera");
loop
select
accept Zamow(Jedzenie: in Typ_Jedzenia; Numer: in Integer; Czy_Zatwierdzono: out Boolean) do
Put_Line("Trwa zamawianie...");
end Zamow;
end select;
end loop;
end Buffer;
From my attempts I understand that when I want to call entry Buffer.Zamow(Index_Jedzenia, Nr_Produkcji_Jedzenia, Zatwierdzono); (which is in task Producent) there is an error with 'Zatwierdzono' argument. When I removed this argument from declarations and definitions Zamow() entry worked.
Full error: invalid use of subtype mark in expression or call
What should I change or where is the problem with this boolean Zatwierdzono variable?
Zatwierdzono means Accepted in this case.
Thanks for any ideas.
You have two problems:
Index_Jedzenia := Jedzenie;
In your Start entry is trying to implicitly convert Jedzenie from its type, Typ_Jedzenia, to Integer, the type of Index_Jedzenia. You need some way to convert this.
Additionally on the line you are seeing the error on, the first parameter of that entry is of type Typ_Jedzenia but you are passing in an Integer (Index_Jedzenia is an integer). Again, you can't implicitly convert types like that.
If Typ_Jedzenia is actually an integer, you can explicitly convert them. Otherwise you need to make a conversion function of some type and use that before passing in or assigning to different types.

To lower case recursive algorithm

I have this code for returning a string in all lower case, using recursion:
function min(ch:string):string;
begin
if ( ch = '' ) then
min:= ''
else
if (ch[1] in ['A'..'Z']) then
begin
ch[1]:=chr(ord(ch[1])+32);
min:= ch[1] + min(copy(ch,2,length(ch)-1));
end;
end;
But it doesn't work:
When I run it with the example min('AbC') the output is a only not abc.
Where is the problem here?
The problem is that you do not always call the recursive function. As soon as you find a letter that is already lower case, you don't look any more at the characters that follow it. And so the return value is truncated up to that character.
You should also call it when the character being looked at is not a capital letter. So move the recursive call out of that if, and it will work:
function min(ch:string):string;
begin
if ( ch = '' ) then
min:= ''
else
begin
if (ch[1] in ['A'..'Z']) then
ch[1]:=chr(ord(ch[1])+32);
min:= ch[1] + min(copy(ch,2,length(ch)-1));
end;
end;
See it run on ideone.com.

Ada 95 Check if enter was pressed

I am new to Ada.
How can I check if enter was pressed?
The while loop should be able to check whether the input character is a white space or an enter key.
Furthermore, how can I check the user input type, like the type() or typeof() function in other languages?
FUNCTION ReadValue RETURN Unbounded_String IS
ValueChar : Character;
Result : Unbounded_String := To_Unbounded_String("NULL");
BEGIN
Get(ValueChar);
Skip_Line;
WHILE ValueChar /= ';'LOOP
Get(ValueChar);
IF IsValidNameInput(ValueChar) THEN
Result := Result & ValueChar;
ELSE
exit;
END IF;
END LOOP;
ValueIntegerFlag := CheckValue(Value);
RETURN Result;
END ReadValue;
Read the characters one-at-a-time without special ENTER handling using Get_Immediate instead of Get - ARM A.10.7(9).
You can do checks on the class of the character you’ve just read using Ada.Characters.Handling - ARM A.3.2 - something like
function Is_Valid_Name_Input (Ch : Character) return Boolean is
begin
return Ada.Characters.Handling.Is_Graphic (Ch)
and then not Ada.Characters.Handling.Is_Space (Ch);
end Is_Valid_Name_Input;
(probably not quite what you want, since it makes &*^$$^ a valid name!)
Ada.Characters.Handling.Is_Line_Terminator detects ENTER (on Unix; probably on Windows too).
You can check whether a string corresponds to an integer by trying the conversion and catching the exception when it fails:
function Check_Integer_Value (Str : Unbounded_String) return Boolean is
Dummy : Integer;
begin
Dummy := Integer'Value (To_String (Str));
return True;
exception
when Constraint_Error =>
return False;
end Check_Integer_Value;
With regard to ReadValue:
Don’t initialize Result - it starts off as the empty string (and you really don’t want to start with the string ”NULL”).
It skips the first character input.
What’s that Skip_Line for?
Try something like
function Read_Value return Unbounded_String is
Value_Char : Character;
Result : Unbounded_String;
begin
loop
Get_Immediate (Value_Char);
exit when Value_Char = ';';
if Is_Valid_Name_Input (Value_Char) then
Result := Result & Value_Char;
end if;
end loop;
return Result;
end Read_Value;

Ada actual for "S" must be a variable

So here is a piece of my body file. I am getting the error "words.adb:75:42: actual for "S" must be a variable".
procedure Remove_Character(S : in out Ustring; C : in Character; Successful : out Boolean) is
begin
for I in 1..length(S) loop
if Element(S, I) = C then
Delete(S, I, I);
Successful := true;
return;
end if;
end loop;
Successful := false;
end Remove_Character;
function Is_Subset(Subset : Ustring; S : Ustring) return Boolean is
Could_Remove : Boolean;
begin
for I in 1..length(Subset) loop
Remove_Character(S , Element(Subset, I), Could_Remove);
if Could_Remove = false then
return false;
end if;
end loop;
return True;
end Is_Subset;
I understand where my error is coming from. Remove_Character uses S : in out Ustring while function Is_Subset uses S : in Ustring.
My question is how do I change the variable from Remove_Character into only an in Ustring?
Sorry if this is a tad jumbled, I'm fairly new to both programming and the site.
You can't, at least not directly.
I don't know what a UString is, but I presume the Delete procedure modifies it. If you changed the declaration of S in Remove_Character to S: in Ustring, you'd presumably get an error on the call to Delete.
The simplest approach I can think of would be to make a copy of S in Is_Subset:
Copy_Of_S: UString := S;
and then pass the (modifiable) copy to Remove_Character.
By "simplest", I mean it makes the smallest change to your existing code. But you should probably consider reorganizing it. Determining whether one UString is a subset of another by modifying one of the strings doesn't seem like the best approach; I'm sure there's a more efficient way to do it.
A minor and irrelevant point: this:
if Could_Remove = false then
is better written as:
if not Could_Remove then

Resources