Eiffel: a way to evaluate a STRING expression like `eval` - eval

Is there a way to evaluate a STRING expression in Eiffel? (A big source of error I know... but a powerful mechanism!)
Looking for a way to do a generic setting mechanism for database fields and classes I'm trying to do something like.
fields_mapping: HASH_TABLE [ANY, STRING]
do
create Result.make (10)
Result.put (name, "name")
Result.put (surname, "surname")
...
end
set_fields
do
across
fields_mapping as field
loop
eval("set_" + field.key + "(" + field.item + ")")
end
end
I know I could do it with agents, but it seems for me less generic as I have to define every function 2 times
in the fields_mapping
in another fields_mapping I have for JSON conversion

Existing implementations of Eiffel are compiling rather than interpreting. Therefore, evaluation of arbitrary expressions is not supported. Updating arbitrary object fields using given values is still possible using reflection classes:
update_object (field_values: HASH_TABLE [ANY, STRING]; target: ANY)
-- Update fields of object `target` with values from `field_values`.
local
object: REFLECTED_REFERENCE_OBJECT
fields: like field_properties
position: like {REFLECTED_OBJECT}.field_count
do
create object.make (target)
fields := field_properties (object.dynamic_type)
across
field_values as v
loop
if attached fields [v.key] as f then
position := f.position
if {REFLECTOR}.field_conforms_to (v.item.generating_type.type_id, f.type_id) then
inspect object.field_type (position)
when {REFLECTOR_CONSTANTS}.integer_32_type then
object.set_integer_32_field (position, {INTEGER_32} / v.item)
when ... then
-- Other basic types.
when {REFLECTOR_CONSTANTS}.reference_type then
object.set_reference_field (position, v.item)
else
print ("Unhandled field type%N")
end
else
print ("Field type mismatch%N")
end
end
end
end
The code above is a bit simplified because it does not handle types SPECIAL and TUPLE as well as user-defined expanded types. The code relies on a helper feature that records type information so that next time it should not be recomputed from scratch:
field_properties (type_id: like {TYPE [ANY]}.type_id):
HASH_TABLE [TUPLE [position: INTEGER; type_id: INTEGER], STRING]
-- Positions and types of fields indexed by their name
-- for a specified type ID `type_id`.
local
i: like {REFLECTOR}.field_count_of_type
do
Result := field_positions_table [type_id]
if not attached Result then
from
i := {REFLECTOR}.field_count_of_type (type_id)
create Result.make (i)
field_positions_table.force (Result, type_id)
until
i <= 0
loop
Result [{REFLECTOR}.field_name_of_type (i, type_id)] :=
[i, {REFLECTOR}.field_static_type_of_type (i, type_id)]
i := i - 1
end
end
end
field_positions_table: HASH_TABLE [HASH_TABLE
[TUPLE [position: INTEGER; type_id: INTEGER], STRING], INTEGER]
once
create Result.make (1)
end
Using the feature update_object and assuming an object x has fields foo and bar of types INTEGER_32 and detachable STRING_8 respectively, the following code
field_values: HASH_TABLE [ANY, STRING]
do
create field_values.make (10)
field_values ["foo"] := {INTEGER_32} 5
field_values ["bar"] := " bottles"
update_object (field_values, x)
print (x.foo)
print (x.bar)
will print 5 bottles regardless of the previous state of the object x.

Related

ORA-06533: Subscript beyond count with associative array

I am getting Subscript beyond count error while executing the procedure. I am trying to populate an associative array in my procedure and then eventually use it to insert. I think there is somewhere error in initialization of the array
Type definitions in a package header
TYPE sonic_data_rec IS RECORD(
ep_a_num LOG_PICK.EP_A_NUM%TYPE,
wvy_num LOG_PICK.WVY_NUM%TYPE,
ltrl_name LOG_PICK.Ltrl_Name%TYPE,
vel_pick_sq_num LOG_PICK.w_vel_pick_sq_num%TYPE,
vel_sv_typ_cd LOG_PICK.W_VEL_SV_TYP_CD%TYPE,
vel_sv_dt LOG_PICK.W_VEL_SV_DT%TYPE,
w_vel_log_pick_cd LOG_PICK.w_vel_log_pick_cd%TYPE,
sq_num LOG_PICK.W_VEL_PICK_SQ_NUM%TYPE,
pick_cd LOG_PICK.W_VEL_LOG_PICK_CD%TYPE,
onew_tm LOG_PICK.W_VEL_ONE_WAY_TV_TM%TYPE,
sonic_log LOG_PICK.W_VEL_SOLOG_CALB_VSP%TYPE,
ms_dpth LOG_PICK.W_VEL_PICK_DPTH%TYPE,
tv_dpth LOG_PICK.W_VEL_PICK_DPTH%TYPE);
TYPE sonic_tab IS TABLE OF sonic_data_rec;
Procedure code, only relevant code of package_body posted
PROCEDURE LOAD_LOG_PICK
(p_wvy_num IN WELL_VEL_SURVEY.WVY_NUM%TYPE
,p_noofpts IN NUMBER
,p_data_list IN STRINGARRAY)
IS
l_son_array sonic_tab := sonic_tab();
BEGIN
count1 := 1;
LOOP
l_son_array(count1).ep_a_num := p_ep_a_num;
l_son_array(count1).wvy_num := p_wvy_num;
l_son_array(count1).vel_sv_typ_cd := 'L';
l_son_array(count1).vel_sv_dt := p_pick_date;
l_son_array(count1).vel_pick_sq_num := count1;
l_son_array(count1).w_vel_log_pick_cd := 'O';
l_son_array(count1).ms_dpth := l_ms_dpth;
l_son_array(count1).onew_tm := regexp_substr(p_data_list(count1), '[^ ]+', 1, 1);
l_son_array(count1).sonic_log := regexp_substr(p_data_list(count1), '[^ ]+', 1, 1);
EXIT WHEN count1=5000;
count1 := count1+1;
END LOOP;
You are attempting to populate a nested table, not an associative array.
If sonic_tab was an associative array, it would be declared as
TYPE sonic_tab IS TABLE OF sonic_data_rec INDEX BY BINARY_INTEGER;
See the Oracle documentation on PL/SQL collection types for further information about the different types of collections you can use in PL/SQL.
As you have a nested table, you need to add calls to sonic_tab.EXTEND to make the table larger. It starts with size 0 because when you create it by calling sonic_tab(), you aren't passing any records into the constructor. Either call sonic_tab.EXTEND(1); in each iteration of the loop before you try to add anything to that item of the table, or call sonic_tab.EXTEND(5000); once before the loop if you know in advance how many items you will have in the table.

Is there a way to specify various types for a parameter

Is there a way to restrict the conformance of a type to be a collection of types?
Let me explain by example:
give_foo (garbage: ANY): STRING
do
if attached {STRING} garbage as l_s then
Result := l_s
elseif attached {INTEGER} garbage as l_int then
Result := l_int.out
elseif attached {JSON_OBJECT} garbage as l_int then
Result := l_int.representation
elseif attached {RABBIT} garbage as l_animal then
Result := l_animal.name + l_animal.color
else
Result := ""
check
unchecked_type_that_compiler_should_be_able_to_check_for_me: False
end
end
end
Couldn't I do something like (like a convert function could do)
give_foo (garbage: {STRING, INTEGER, JSON_OBJECT, RABBIT}): STRING
do
if attached {STRING} garbage as l_s then
Result := l_s
elseif attached {INTEGER} garbage as l_int then
Result := l_int.out
elseif attached {JSON_OBJECT} garbage as l_int then
Result := l_int.representation
elseif attached {RABBIT} garbage as l_animal then
Result := l_animal.name + l_animal.color
else
Result := ""
check
unchecked_type_that_compiler_should_be_able_to_check_for_me: False
end
end
end
or something like
not_garbage_hash_table: HASH_TABLE[{INTEGER, STRING, ANIMAL}, STRING]
Conformance to a collection of types is not supported for several reasons:
Calling a feature on an expression of such a type becomes ambiguous because the same name could refer to completely unrelated features.
In one case we need a sum (disjoint union) of types, in the second - plain union, in the third - an intersection, etc. And then, there could be combinations. One would need an algebra built on top of a type system that becomes too complicated.
If the requirement is to check that an argument is one of expected types, the following precondition can be used:
across {ARRAY [TYPE [detachable ANY]]}
<<{detachable STRING}, {INTEGER}, {detachable JSON_OBJECT}>> as t
some argument.generating_type.conforms_to (t.item) end
A common practice to process an expression of a potentially unknown type is a visitor pattern that deals with known cases and falls back to a default for unknown ones.
Possibly place Alexander's solution in a BOOLEAN query so it can be reused?
is_string_integer_or_json_object (v: detachable ANY): BOOLEAN
-- Does `v' conform to {STRING}, {INTEGER}, or {JSON_OBJECT}?
do
across {ARRAY [TYPE [detachable ANY]]}
<<{detachable STRING}, {INTEGER}, {detachable JSON_OBJECT}>> as t
some v.generating_type.conforms_to (t.item) end
end

Creating a 2D-Array Field in an Ada Tagged Type

I'm trying to create a Java/C class equivalent in Ada. From what I've researched -- the equivalent would be a Tagged Record. I'm wanting to create a field that is a 2D Array, however with the code below -- it gives me the following errors.
Code:
package Foo is
type Bar is tagged
record
field1 : Integer;
field2 : Integer;
type field3 is array (1 .. 10, 1 .. 5) of Integer;
end record;
end Foo;
Error:
foo.ads:6:25: missing "end record;" for "record" at line 8
foo.ads:7:17: no "record" for this "end record"
This tells me that "Anonymous arrays are not allowed as components".
package Foo is
type Bar is tagged
record
field1 : Integer;
field2 : Integer;
field3 : array (1 .. 10, 1 .. 5) of Integer;
end record;
end Foo;
The reason your second version fails is that
field3 : array (1 .. 10, 1 .. 5) of Integer;
declares a new, unnamed (anonymous), type (array (1 .. 10, 1 .. 5) of Integer), and you’re not allowed to nest type declarations.
Depending on your application, you might try
type Twod_Array is array (Integer range <>, Integer range <>) of Integer;
type Bar is tagged
record
field1 : Integer;
field2 : Integer;
field3 : Twod_Array (1 .. 10, 1 .. 5);
end record;
or
type Twod_Array is array (Positive range <>, Positive range <>) of Integer;
type Bar (First, Second : Natural) is tagged
record
field1 : Integer;
field2 : Integer;
field3 : Twod_Array (1 .. First, 1 .. Second);
end record;
[Hmm. I said First, Second : Natural to allow for zero-length arrays. But I’m not sure what the point of A_Bar : Bar (0, 10); would be!]
You attempt to declare one named type inside the declaration of another named type. You can't do that.
Also, Ada does not allow record fields of anonymous types, so you have to ensure that you have declared all the types you need for the fields of your record, before the declaration of the record itself.
Are you sure you need values in the range -32_768 .. 32_767 (that's all Ada promises about the type Integer)? Normally one would declare a type matching the requirements of the problem. (Of course, if the problem is to match the C type int closely, then one should use Interfaces.C.int.)

Ada: packing record with variable sized array

I am looking to create a packed record that can hold an array the varies in length from 5 - 50 elements. Is it possible to do this in such a way that the record can be packed with no wasted space? I will know how many elements will be in the array when I go to create the record.
-- the range of the array
type Array_Range_T is Integer range 5 .. 50;
-- the array type
type Array_Type_T is array range (Array_Range_T) of Integer;
-- the record
type My_Record_T (Array_Length : Integer := 5) is
record
-- OTHER DATA HERE
The_Array : Array_Type_T(Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 10 range 0 .. Array_Length * 16;
end record;
for My_Record_T'Size use 80 + (Array_Length * 16);
This obviously won't compile, but shows the spirit of what I am trying to do. If possible I would like to keep the length of the array out of the record.
Thank you!
There really isn't a way in Ada to represent the record the way you're asking for. However, since your concern really isn't with how the record is represented in memory, but rather with how it's transmitted to a socket, you probably don't need to worry about record representation clauses.
Instead, you can define your own Write routine:
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
for My_Record_T'Write use Write;
or, I believe this will work in Ada 2012:
type My_Record_T is record
...
end record
with Write => Write;
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
and then the body will look like
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T) is
begin
-- Write out the record components, EXCEPT Array_Length and The_Array.
T1'Write (Stream, Item.F1); -- F1 is a record field, T1 is its type
T2'Write (Stream, Item.F2); -- F2 is a record field, T2 is its type
...
-- Now write the desired data
declare
Data_To_Write : Array_Type_T (1 .. Item.Array_Length)
renames Item.The_Array (1 .. Item.Array_Length);
-- I'm assuming the lower bound is 1, but if not, adjust your code
-- accordingly
begin
Array_Type_T'Write (Stream, Data_To_Write);
-- Note: using 'Write will write just the data, without any bound
-- information, which is what you want.
end;
end Write;
This won't work if the other components need to be packed, though, e.g. if you want to write a byte to the socket that contains one 3-bit record component and one 5-bit record component. If that's necessary, I don't think the built-in 'Write attributes will do that for you; you may need to do your own bit-twiddling, or you could get tricky and define an array of Stream_Elements and use an Address clause or aspect to define an array that overlays the rest of the record. But I wouldn't use the overlay method unless I were 100% certain that the reader at the other end of the socket were an Ada program that uses the exact same type definition.
Note: I haven't tested this.
Not sure I completely understand what you're trying to achieve but can't you do something like this
-- the range of the array
type Array_Range_T is range 1 .. 50;
-- the array type
type Array_Type_T is array (Array_Range_T range <>) of Integer;
Array_Length : constant := 5; --5 elements in the array
-- the record
type My_Record_T is
record
-- OTHER DATA HERE
The_Array : Array_Type_T (1 .. Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 0 range 0 .. (Array_Length * Integer'Size) - 1 ;
end record;
for My_Record_T'Size use (Array_Length * Integer'Size);

What data structure to use in order to sort this data in PL/SQL?

This is Oracle 11.2g. In a PL/SQL function, I've got a loop whereby each iteration, I create a string and an integer associated with that string. The function returns the final concatenation of all the generated strings, sorted (depending on a function input parameter), either alphabetically or by the value of the integer. To give an idea, I'm generating something like this:
Iteration String Integer
1 Oslo 40
2 Berlin 74
3 Rome 25
4 Paris 10
If the input parameter says to sort alphabetically, the function output should look like this :
Berlin, Oslo, Paris, Rome
Otherwise, we return the concatenated strings sorted by the value of the associated integer:
Paris, Rome, Oslo, Berlin
What is the most appropriate data structure to achieve this sort? I've looked at collections, associative arrays and even varrays. I've been kind of shocked how difficult this seems to be to achieve in Oracle. I saw this question but it doesn't work in my case, as I need to be able to sort by both index and value: How to sort an associative array in PL/SQL? Is there a more appropriate data structure for this scenario, and how would you sort it?
Thanks!
It is very easy if you use PL/SQL as SQL and not like other languages. It is quite specific and sometimes is very nice exactly because of that.
Sometimes I really hate PL/SQL, but this case is absolutely about love.
See how easy it is:
create type it as object (
iter number,
stringval varchar2(100),
intval integer
);
create type t_it as table of it;
declare
t t_it := new t_it();
tmp1 varchar2(32767);
tmp2 varchar2(32767);
begin
t.extend(4);
t(1) := new it(1,'Oslo',40);
t(2) := new it(2,'Berlin',74);
t(3) := new it(3,'Rome',25);
t(4) := new it(4,'Paris',10);
select listagg(stringval,', ') within group (order by stringval),
listagg(stringval,', ') within group (order by intval)
into tmp1, tmp2
from table(t);
dbms_output.put_line(tmp1);
dbms_output.put_line(tmp2);
end;
/
drop type t_it;
drop type it;
Here you can see the problem that you must create global types, and this is what I hate it for. But they say in Oracle 12 it can be done with locally defined types so I am waiting for it :)
The output is:
Berlin, Oslo, Paris, Rome
Paris, Rome, Oslo, Berlin
EDIT
As far as you do not know the amount of iterations from the beginning the only way is to do extend on each iteration (this is only example of extending):
declare
iterator pls_integer := 1;
begin
/* some type of loop*/ loop
t.extend();
-- one way to assign
t(t.last) := new it(1,'Oslo',40);
-- another way is to use some integer iterator
t(iterator) := new it(1,'Oslo',40);
iterator := iterator + 1;
end loop;
end;
I prefer the second way because it is faster (does not calculate .last on each iteration).
This is an example of pure PL/SQL implementation that is based on the idea associative array (aka map or dictionary in other domains) is an ordered collection that is sorted by a key. That is a powerful feature that I have used multiple times. For input data structure in this example I decided to use a nested table of records (aka a list of records).
In this particular case however I'd probably go for similar implementation than in simon's answer.
create or replace package so36 is
-- input data structures
type rec_t is record (
iter number,
str varchar2(20),
int number
);
type rec_list_t is table of rec_t;
function to_str(p_list in rec_list_t, p_sort in varchar2 default 'S')
return varchar2;
end;
/
show errors
create or replace package body so36 is
function to_str(p_list in rec_list_t, p_sort in varchar2 default 'S')
return varchar2 is
v_sep constant varchar2(2) := ', ';
v_ret varchar2(32767);
begin
if p_sort = 'S' then
-- create associative array (map) v_map where key is rec_t.str
-- this means the records are sorted by rec_t.str
declare
type map_t is table of rec_t index by varchar2(20);
v_map map_t;
v_key varchar2(20);
begin
-- populate the map
for i in p_list.first .. p_list.last loop
v_map(p_list(i).str) := p_list(i);
end loop;
v_key := v_map.first;
-- generate output string
while v_key is not null loop
v_ret := v_ret || v_map(v_key).str || v_sep;
v_key := v_map.next(v_key);
end loop;
end;
elsif p_sort = 'I' then
-- this branch is identical except the associative array's key is
-- rec_t.int and thus the records are sorted by rec_t.int
declare
type map_t is table of rec_t index by pls_integer;
v_map map_t;
v_key pls_integer;
begin
for i in p_list.first .. p_list.last loop
v_map(p_list(i).int) := p_list(i);
end loop;
v_key := v_map.first;
while v_key is not null loop
v_ret := v_ret || v_map(v_key).str || v_sep;
v_key := v_map.next(v_key);
end loop;
end;
end if;
return rtrim(v_ret, v_sep);
end;
end;
/
show errors
declare
v_list so36.rec_list_t := so36.rec_list_t();
v_item so36.rec_t;
begin
v_item.iter := 1;
v_item.str := 'Oslo';
v_item.int := 40;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 2;
v_item.str := 'Berlin';
v_item.int := 74;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 3;
v_item.str := 'Rome';
v_item.int := 25;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 4;
v_item.str := 'Paris';
v_item.int := 10;
v_list.extend(1);
v_list(v_list.last) := v_item;
dbms_output.put_line(so36.to_str(v_list));
dbms_output.put_line(so36.to_str(v_list, 'I'));
end;
/
show errors

Resources