Finnian Reilly
EROS is an experimental client-server application framework that is part of the Eiffel Loop collection of libraries. It was created for the purpose of writing client-server applications in which multiple clients can remotely call Eiffel routines using an original XML orientated protocol. Unlike XML-RPC there is no special XML grammar apart from a single XML processing instruction (<?call .. ?>) containing a routine call with a very Eiffel like syntax. The call instruction appears after the XML declaration and before the root tag of an optional application specific XML document. EROS has it's own network protocol based on a small set of commands (2) and message types (4).
Developing an EROS server application is done by implementing a number of descendants of the class EL_REMOTELY_ACCESSIBLE. Implementing this class defines a number of routines as being remotely callable and a number of types that can be passed as arguments buildable from XML. You can test your classes using a light weight single threaded server implemented with class EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLER. You are then ready to deploy your classes in the multi-threaded server which provides the following features:
An application is provided which demonstrates wave generation and
fourier analysis based on Eiffeler Greg Lee's Numeric Eiffel Library
library (NEL). A slightly modified
form of the NEL fft module is distributed with the Eiffel Loop
libraries to ease some inheritance problems. Two remote classes are
implemented,
SIGNAL_MATH
which
generates wave forms and FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE
which
transform the wave forms.
http://www.eiffel-loop.com/images/EROS-server-screenshot.png
The EROS server exchanges plain text messages with it's clients across a network socket. Each message is either a command or a data string. A client connects to the server for a session and makes as many routine calls as it needs to before sending a quit command. The reply from each call is either an error setting command, a procedure finished acknowledgement, or in the case of a function call: either an XML document, or an Eiffel string representation of a numeric, boolean or string type. At present EROS does not support user authentication so is only suited for use within a secure network.
Here are some examples of EROS remote routine call requests. For clarity the XML declarations (<?xml ..?>) are omitted. The syntax is similar in a way to Eiffel agents in that a class name in braces is used to specify an open argument place holder. Classes SIGNAL_MATH and FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE both inherit from class EL_REMOTELY_ACCESSIBLE.
<?call {ADDRESS_BOOK}.set_email ('John Smith', 'john.smith78@gmail.com')?>
<?call {SIGNAL_MATH}.cosine_waveform (4, 7, 0.5)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.is_power_of_two (128)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.fft_make (128)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_windower (Rectangular_windower)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.is_valid_input_length (128)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.is_output_length_valid?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.do_transform?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.output?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_input ({E2X_VECTOR_COMPLEX_DOUBLE})?>
<vector-complex-double count="128">
<col real="-1" imag="0"/>
<col real="-0.98078528040323043" imag="0"/>
<col real="-0.92387953251128674" imag="0"/>
<col real="-0.83146961230254524" imag="0"/>
..
</vector-complex-double>
The final example has a document body which is
deserialized to an
object of type E2X_VECTOR_COMPLEX_DOUBLE and then passed as an
argument to set_input. Some of the calls (for example:
is_power_of_two) are intended for use in preconditions on the
client. In the set_windower call example, the constant
Rectangular_windower refers
to a once function in class FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE.
The fft_make and
do_transform examples
are the only 2 examples of a procedure call. For procedures a 'procedure-ok'
string is returned to the client to indicate it executed
without error. All the other examples are
function calls returning either a boolean string or an XML document
representing class E2X_VECTOR_COMPLEX_DOUBLE. If
an error occurred the returned message is a set_error
command.
EROS makes use of the Eiffel Loop multi-threaded logging framework. This logger allows you to see output which is indented to reflect the state of the call stack. It shows the entry and exit from routines in a way that is designed to mimic the routines code definition. There is a global filtering mechanism that allows you to see output only from selected classes, and within a class you can further filter by routine name. You can of course turn off all output when you require the last bit of performance in your application. The logger can be optionally integrated with a Vision2 or WEL toolbar allowing control over which thread output is being output in the command console.
The server is launched with some
command line arguments specifying the port to listen for connections on
and the maximum number of launch-able threads to process requests.
Connections in excess of that number are queued until a thread
becomes available.
Switching to a different thread displays only the last 50 or so
lines of logged output. If you wish to see all the logged output for
the currently selected thread since the application launched, use the
refresh button to refresh the console terminal. (note: this will
temporarily block the thread while it is refreshing)
An EROS server application is developed by creating one or more classes which conform to the type: EL_REMOTELY_ACCESSIBLE. These classes are then linked into the EROS server framework by implementing a routine in the root class:
Callable_classes: ARRAY [like remote_object_maker] is
--
do
Result := <<
create {EL_REMOTE_OBJECT_MAKER [FOO]},
create {EL_REMOTE_OBJECT_MAKER [BAR]},
..
>>
end
Unless an error occurs the remote routine result is either an XML document or a string representation of an Eiffel numeric or string. An XML document result is created by calling the function to_xml on a result object that conforms to the type EL_SERIALIZEABLE_AS_XML. You can either use your own serialization scheme by implementing to_xml or avail of the textual serialization framework provided by one of the modules from Eiffel Loop. The mainstay of this framework is a template substitution language called Evolicity. Evolicity is an Eiffelized version of the Apache Velocity project, a template substitution language for Java.
The fourier math example provides a remote interface to classes SIGNAL_MATH and FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE. The root class for the application looks like this:
class
FOURIER_MATH_SERVER_APP
inherit
EL_REMOTE_ROUTINE_CALL_SERVER_APPLICATION
create
make
feature {NONE} -- Implementation
Callable_classes: ARRAY [like remote_object_maker] is
--
do
Result := <<
create {EL_REMOTE_OBJECT_MAKER [SIGNAL_MATH]},
create {EL_REMOTE_OBJECT_MAKER [FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE]}
>>
end
feature {NONE} -- Constants
name: STRING is "Fourier Transform Math"
Log_filter: ARRAY [TUPLE] is
--
once
Result := <<
["FOURIER_MATH_SERVER_APP", "*"],
["SIGNAL_MATH", "*"],
["FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE", "*"],
["EL_REMOTE_ROUTINE_CALL_SERVER_MAIN_WINDOW", "*"],
["EL_REMOTE_CALL_REQUEST_DELEGATING_CONSUMER_THREAD", "*"],
["EL_REMOTE_CALL_CONNECTION_MANAGER_THREAD", "*"],
["EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLING_THREAD", "*"],
["EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLER", "*"],
["EL_REMOTE_CALL_CLIENT_CONNECTION_QUEUE", "*"],
["EL_SERVER_ACTIVITY_DISPLAY_REFRESHER", "prompt_update", "update"]
>>
end
end
The class EL_REMOTE_OBJECT_MAKER is a factory class
taking a
type parameter conforming to EL_REMOTELY_ACCESSIBLE. The log
filter
array contains a list of classes to show logging for. The class: EL_SERVER_ACTIVITY_DISPLAY_REFRESHER
has logging restricted to two routines. The other classes have no
restrictions on which routines are logged.
When the server is active a client can connect to the server and make
calls to either SIGNAL_MATH or FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE
for the duration of a session.
The steps for making the routines of a class remotely accessible in an EROS server are as follows:
Create a new class that inherits from class EL_REMOTELY_ACCESSIBLE.
Implement procedures on_session_begin and on_session_end to respectively reinitialize the object to start a new client session and do clean up at the end of a session. During a session multiple calls may be made to an object which may change the state of an object. In the next session a different client could be connected. The procedure on_session_begin allows you to reset the object to a beginning state. If you don't have a use for these procedures inheriting the class EL_DEFAULT_SESSION_EVENTS implements do nothing behavior.
Declare remotely
callable procedures and functions by implementing the functions
array and
procedures array
as functions. (Note: these should not be once functions.)
Each array
contains a list of tuples
mapping a remotely callable function or procedure name to an Eiffel
agent. You also need to declare an array of argument deserialization
functions in the array creation_functions. These are functions
which build an Eiffel argument object for a remote call from the XML
data in a remote call message.
For example:
procedures: ARRAY [like procedure_mapping] is
--
do
Result := <<
["go", agent go],
["bo", agent bo],
..
>>
end
functions: ARRAY [like function_mapping] is
--
do
Result := <<
["foo", agent foo],
["bar", agent bar],
..
>>
end
The EROS server
framework uses information from the function
empty_operands in class ROUTINE (the return type of
the agent to do basic type checking against the
arguments sent
by the client.
The result type of functions defined as being remotely callable must
meet one of the following conditions:
In the example below class FOOBAR has
a creation function create_FOOBAR and likewise class GOBO.
creation_functions: ARRAY [like creation_function_mapping] is
--
do
Result := <<
["{FOOBAR}", agent create_FOOBAR],
["{GOBO}", agent create_GOBO],
..
>>
end
create_FOOBAR (str: STRING): FOOBAR
--
do
create Result.make_from_string (str)
end
create_GOBO (str: STRING): GOBO
--
do
create Result.make_from_string (str)
end
If there is some routine foo which takes type FOOBAR as
an argument, a call message would look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<?call foo ({FOOBAR})?>
<foobar-data>
..
</foobar-data>
Here the argument to foo is constructed from the XML
document data foobar-data. Also there is no reason why a
routine can't have two buildable arguments both constructed from the
same document. There would however be a slight overhead of the document
being parsed an extra time for the second argument.
EROS makes use of the Eiffel Loop XML deserialization framework in order to construct Eiffel objects from the XML data in call requests. Hence forth referred to as ELXD, this framework works by traversing an XML document and comparing an auto-constructed xpath for each node to a developer defined table of XPaths mapped to Eiffel agents. When it finds a node that matches one of the xpaths, the corresponding agent is called. The agent procedure has access to the node text and name and can use this to set an object value or extend a list. This sounds like it might introduce a lot of processing overhead but it doesn't. This is because the xpath for each node is constructed according to clearly defined rules ensuring the uniqueness of each xpath. By assuming that the xpaths declared in the table conform to these rules, the matching process is simply a hash table lookup for each node's xpath. The rules are few and simple as only a small subset of the xpath language is used. The XML and xpath tables example below illustrate allowable syntax that ELXD is able to match. ELXD also allows element selection by the value of an attribute.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta name="description" content="Free Web tutorials" />
<meta name="keywords" content="HTML,CSS,XML,JavaScript" />
<title>XHTML Example</title>
</head>
<body>
<p>Please Choose a Day:<br/><br/>
<select name="day">
<option selected="selected">Monday</option>
<option>Tuesday</option>
<option>Wednesday</option>
</select>
</p>
</body>
</html>
@xmlns
@lang
title/text()
body/p/text()
body/select/@name
body/select/option/text()
head/meta[@name='description']/@content
head/meta[@name='keywords']/@content
body/select/option[@selected='selected']/text()
head
body
body/p/br
body/p/select[@name='day']
../processing-instruction('foobar')
To create an Eiffel class representing this XHTML document, say XHTML_DOC, you inherit from class EL_BUILDABLE_FROM_XML and redefine object_building_actions as a once function containing a list of xpath strings mapped to object construction agents. What the construction procedures do is completely up to the developer. You also need to define the name of the root element. This object contains the actual XML parser and is shared between all the instances of XHTML_DOC. The function Object_building_actions and attribute Root_node_name for the above example might look like the following:
class XHTML_DOC
inherit
EL_BUILDABLE_FROM_XML
create
make_from_string, make_from_file, make_from_stream
feature {NONE} Implementation
Object_building_actions: EL_EIF_OBJ_BUILDING_ACTION_TABLE is
--
once
create Result.make (<<
["../processing-instruction('foobar')", agent initialize],
["head", agent create_header],
["head/meta[@name='description']/@content", agent set_header_description],
["head/meta[@name='keywords']/@content", agent set_header_keywords],
["title/text()", agent set_title],
["body/p/text()", agent add_body_paragraph],
["body/p/br", agent add_paragraph_line_break],
["body/select/@name", agent set_dropdown_list_name],
["body/select/option/text()", agent add_option_to_dropdown_list]
>>)
end
Root_node_name: STRING is "html"
EL_BUILDABLE_FROM_XML has an attribute node: EL_XML_NODE which can be accessed by the construction procedures to obtain the name and value of the current node. EL_XML_NODE has various conversion functions to obtain a node value as type: STRING, INTEGER, REAL or DOUBLE.
The processing-instruction xpath is experimental (or a solution in search of a problem) and can be used to obtain the value of a processing instruction which is a sibling of the root element (i.e. before or after the root element). For the purposes of EROS it was more convenient to extract the call processing instruction using the search functions in the standard Eiffel string class. This is the only kind of xpath where you can use the parent-of-operator represent by two dots '..'.
After you have defined all the construction
procedures in XHTML_DOC and
mapped them to xpaths, you can create an instance of XHTML_DOC
from an XML string or file with one of the following creation
procedures:
In addition to the EROS demo application, more ELXD examples are provided as part of the Eiffel Loop library in project apps/test/test.ecf. See sub-application: XML_TO_EIFFEL_OBJECT_BUILDER_TEST_APP and RECURSIVE_XML_TO_EIFFEL_OBJECT_BUILDER_TEST_APP. The latter demonstrates building from recursively structured XML which brings us to the next topic.
To make complex XML documents more manageable, ELXD allows you to partition the document into multiple xpath contexts which maybe be recursively nested. Each context represents a document fragment meaning a node sub tree of the document. The number of additional contexts is completely up to the developer, so you can represent the XML using just 1 class or 20 classes (actually there is no upper limit). This is a powerful feature as it allows complete flexibility on how you want to represent the document in Eiffel. For example in our XHTHML example we might wish to deserialize the select tag in a separate context defined by a class DROPDOWN_LIST. To do this you inherit EL_EIF_OBJ_BUILDER_CONTEXT in class DROPDOWN_LIST and define object_building_actions with xpaths relative to the select tag. For example:
Object_building_actions: EL_EIF_OBJ_BUILDING_ACTION_TABLE is
--
once
create Result.make (<<
["@name", agent set_name],
["option/text()", agent add_option]
>>)
end
In the Object_building_actions function of class XHTML_DOC you add a line mapping the desired context element to an agent that creates the new context object and switches the parser into that context. For example:
["body/select", agent create_dropdown_list]
Procedure create_dropdown_list creates an instance of class DROPDOWN_LIST and then calls set_next_context with the new DROPDOWN_LIST context as an argument. create_dropdown_list might look something like this.
create_dropdown_list is
--
do
create dropdown_list.make
set_next_context (dropdown_list)
end
When the closing tag for the select element is encountered, the ELXD parser automatically pops back up to the root context. If you need to perform any additional tasks before leaving the context you can redefine procedure on_context_exit in class EL_EIF_OBJ_BUILDER_CONTEXT to perform those tasks. Alternatively you can perform those tasks from the parent context by redefining on_context_return. The argument supplied is the child context just returned from. You have to do an assignment test using the if {x: <class-name>} y then construct where y is of type EL_EIF_OBJ_BUILDER_CONTEXT because y could be any one of a number of different child contexts. In our example x is type DROPDOWN_LIST.
Another possibility for this context switching facility is it allows you to have a recursively defined Eiffel class which is deserializable from recursively structured XML. A recursively defined class is a class which contains attribute features which are derived from the same type as the class itself. This scenario is quite easy to manage with ELXD.Having looked at the mechanics of implementing EL_BUILDABLE_FROM_XML we are now ready to see how this applies to passing an XML document as an argument to a remote routine implemented in an EROS server. The principle example provided in the EROS demo application is the routine set_input from class FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE. It sets an input vector for fourier transformation.
In the remote call request example below, set_input contains a class placeholder {E2X_VECTOR_COMPLEX_DOUBLE}. This indicates to the EROS server that the argument to set_input should be provided by deserializing the XML document data to an object of type E2X_VECTOR_COMPLEX_DOUBLE.
Although it is more usual to have only one argument buildable from XML, it is possible to have more than one. A second one might skim data from a different part of the document. However this would necessitate multiple parsing of the same document. A workaround for this inefficiency would be to merge all the arguments types by creating a wrapper class. This wrapper class would then become the one and only argument type buildable from XML. You can use the context switching facility covered in the previous section to partition the document into document node sub trees and assign each one to a different class.Class E2X_VECTOR_COMPLEX_DOUBLE inherits from VECTOR_COMPLEX_DOUBLE and EL_BUILDABLE_FROM_XML. It adds all the ELXD construction procedures necessary to build objects from XML.
<?xml version="1.0" encoding="ISO-8859-1"?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_input ({E2X_VECTOR_COMPLEX_DOUBLE})?>
<vector-complex-double count="128">
<col real="-1" imag="0"/>
<col real="-0.98078528040323043" imag="0"/>
<col real="-0.92387953251128674" imag="0"/>
<col real="-0.83146961230254524" imag="0"/>
etc..
</vector-complex-double>
Looking at the actual messages sent by the client in the fourier math demo application, you may have noticed an extra processing instruction after the call instruction: <?type row?> or <?type col?>. This was put in mainly to demonstrate the ELXD xpath construct:
../processing-instruction(<instruction name>)
It is used in E2X_VECTOR_COMPLEX_DOUBLE to specify whether the vector is a row or column type. This could just as easily have been implemented as an attribute in element vector-complex-double and is quite inessential to implementing EROS messages. To keep things simple it has been omitted from the above example.
In the remote class FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE, set_input is implemented by adding an entry to the implementation of the procedures array from the inherited class EL_REMOTELY_ACCESSIBLE:
procedures: ARRAY [like procedure_mapping] is
--
do
Result := <<
["set_input", agent set_input],
..
>>
end
We also have to
declare a
deserializer (or builder) for class E2X_VECTOR_COMPLEX_DOUBLE
by adding an entry to the creation_functions array
when implementing this function from EL_REMOTELY_ACCESSIBLE.
The function
create_E2X_VECTOR_COMPLEX_DOUBLE returns
a new instance of class E2X_VECTOR_COMPLEX_DOUBLE with the
make_from_string creation procedure.
creation_functions: ARRAY [like creation_function_mapping] is
--
do
Result := <<
["{E2X_VECTOR_COMPLEX_DOUBLE}", agent create_E2X_VECTOR_COMPLEX_DOUBLE],
..
>>
end
create_E2X_VECTOR_COMPLEX_DOUBLE (str: STRING): E2X_VECTOR_COMPLEX_DOUBLE is
--
do
create Result.make_from_string (str)
end
EROS result types which are not strings or numeric types must
implement EL_SERIALIZEABLE_AS_XML. Implementing EL_SERIALIZEABLE_AS_XML
only requires that you implement the function to_xml which
returns an XML string representation of the result object. You are free
to use any serialization scheme you wish but you are encouraged to try
out the
standard Eiffel Loop serialization framework which uses a text
substitution language called Evolicity. This is an Eiffelized version
of the Java orientated language, Velocity from the Apache open source
collection.
<?xml version="1.0" encoding="ISO-8859-1"?>Class E2X_VECTOR_COMPLEX_DOUBLE uses the following Evolicity scripting template to produce this XML:
<?type col?>
<vector-complex-double count="3">
<col real="2.2" imag="3"/>
<col real="2.2" imag="6.03"/>
<col real="1.1" imag="3.5"/>
</vector-complex-double>
XML_template: STRING isThis Evolicity example can be understood by reference to the following rules of the Evolicity language:
-- Substitution template
"[
<?xml version="1.0" encoding="ISO-8859-1"?>
<?type $vector_type?>
<vector-complex-double count="$count">
#foreach $item in $complex_double_list loop
<$vector_type real="$item.real" imag="$item.imag"/>
#end
</vector-complex-double>
]"
#foreach <iteration item variable> in <sequence> loop
<directive statements or text>
#end
Getter_functions: EVOLICITY_FUNCTION_ACTION_TABLE isThe definitions of the getter functions are as follows:
--
once
create Result.make (<<
["count", agent get_count],
["vector_type", agent get_vector_type],
["complex_double_list", agent get_complex_double_list]
>>)
end
feature {NONE} -- Evolicity reflection
get_vector_type: STRING is
-- result is either 'col' or 'row'
do
Result := Vector_types [type]
end
get_complex_double_list: SEQUENCE [E2X_COMPLEX_DOUBLE] is
--
do
create {VECTOR_COMPLEX_DOUBLE_SEQUENCE} Result.make_from_vector (Current)
end
get_count: INTEGER_REF is
--
do
Result := count.to_reference
end
Note that count must first be converted to a reference before
it can be bound to an Evolicity variable. The same is true of other
expanded numeric and boolean types. Class E2X_COMPLEX_DOUBLE is
an Evolicity context based on class COMPLEX_DOUBLE.
classClass VECTOR_COMPLEX_DOUBLE_SEQUENCE is a an adapter class to enable iteration over the COMPLEX_DOUBLE items of class VECTOR_COMPLEX_DOUBLE (inherited by E2X_VECTOR_COMPLEX_DOUBLE). The underlying implementation of VECTOR_COMPLEX_DOUBLE is an array of doubles: ARRAY [DOUBLE]. Class VECTOR_COMPLEX_DOUBLE_SEQUENCE shares the array memory area of E2X_VECTOR_COMPLEX_DOUBLE and during iteration converts each item on the fly to type E2X_VECTOR_COMPLEX_DOUBLE.
E2X_COMPLEX_DOUBLE
inherit
COMPLEX_DOUBLE
EVOLICITY_EIFFEL_CONTEXT
undefine
is_equal, out
end
feature {NONE} -- Evolicity reflection
get_real: DOUBLE_REF is
--
do
Result := r.to_reference
end
get_imag: DOUBLE_REF is
--
do
Result := i.to_reference
end
Getter_functions: EVOLICITY_GETTER_FUNCTION_TABLE is
--
once
create Result.make (<<
["real", agent get_real],
["imag", agent get_imag]
>>)
end
end
egrep -l '(EVOLICITY_EIFFEL_CONTEXT|EVOLICITY_SERIALIZEABLE)' --include=*.e -r Eiffel-Loop(Total of 25 files)
#if <boolean expression> thenCurrently only a limited range of boolean expressions are possible. A future release will implement a fully complete expression parser. For the time being the following types of non-recursive expression are supported:
<statement block>
#else
<statement block>
#end
#foreach <variable> in <sequence variable> loopRepeats the block of directives for each item in the sequence. The sequence variable must refer to an Eiffel type conforming to SEQUENCE [ANY]. If the sequence item conforms to type EVOLICITY_CONTEXT then the dot operator '.' can be used to reference variables in the item context. Otherwise it is assumed that the item can be converted to a string by calling the out function from the universal parent class ANY.
<directive block>
#end
#foreach $item in $number_spellings loopOutput:
$loop_index of $number_spellings.count: "$item"
#end
1 of 3: "one"loop_index is a predefined variable (see below)
2 of 3: "two"
3 of 3: "three"
#foreach $item in $list loopHere each list item conforms to type EVOLICITY_SERIALIZEABLE_AS_XML. The to_xml variable is predefined and does not need to be declared in the Getter_functions table of contexts in the sequence list.
$item.to_xml
#end
In addition to numeric constants, string constants and objects deserialized from XML, EROS provides for one other kind of argument to an EROS routine, namely constants which are the result of a once function in the target object class.
In the fourier analysis demo you can specify one of two possible windowing functions represented as instances of the class RECTANGULAR_WINDOWER_DOUBLE and DEFAULT_WINDOWER_DOUBLE. A reference to each is supplied in class FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE as the result of once functions Rectangular_windower and Default_windower. When implementing the deferred function functions from class EL_REMOTELY_ACCESSIBLE you must specify a name to agent mapping for each once function that can be referenced in a remote procedure call. This is in addition to the mappings for remotely callable functions.
functions: ARRAY [like function_mapping] is
--
do
Result := <<
..
["Rectangular_windower", agent Rectangular_windower],
["Default_windower", agent Default_windower],
..
>>
end
The procedure set_windower can then be remotely called with either a rectangular or default windower as follows:
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_windower (Default_windower)?>
<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_windower (Rectangular_windower)?>
For any
named constants used like this, the context referred to is always the
remote class being called. A more explicit but currently unsupported
syntax would be as follows:<?call {FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.set_windower (
{FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE}.Default_windower
)
?>
Another hypothetical use case for named constants might be to specify a
color code constant in a drawing routine for a
scalable vector graphics application. Writing an EROS remote routine is pretty much the
same as writing a normal routine. The only difference is
if one of the argument types or result types is NOT one of the basic
types: integer (any width), real (any width), boolean or string. In the
case of a non-basic argument type it must be buildable from XML using a
creation function listed in the creation_function array. For
non-basic result types the type must conform to EL_SERIALIZEABLE_AS_XML
and implement the to_xml function serializing the data as XML.
For an argument which is not a basic type there is one other possibility which is that it could be a named constant referring to a once function. In that case the argument type will be the same as the once function result type. (See section: Once function constants as EROS routine arguments)
When the EROS server receives a result to send back to the client it first checks whether the type conforms to EL_SERIALIZEABLE_AS_XML. If it does it calls the to_xml function and sends the resulting string back to the client.
Failing that, EROS next checks
whether the type conforms to type STRING in which case this is
the result returned to the client.
Failing that EROS assumes that the result can be serialized to a string by calling the the out function from the universal ancestor class ANY. Numeric and boolean results fall into this category.
EROS makes extensive use of the Eiffel Loop logging framework. The
beauty of this framework is that the output is indented to show the
entry and exit from routines. Each entry and exit to a routine is
documented with a header and trailer output text based on the class
name and routine name. The following is some sample output from a test
program for the Eiffel Loop VTD-XML
API. The test function executes an xpath query looking for http urls in
an XML document.
1> VTD_XML_TEST_APP.test_bio_2 (The code which produced the above output is as follows:
1> argument (1) = "//value[@type='url' and contains (text(), 'http://')]"
1> ) is
1> doing
1>
1> EL_XPATH_ROOT_NODE_CONTEXT.context_list is
1> doing
1>
1> end -- EL_XPATH_ROOT_NODE_CONTEXT
1> http://iubio.bio.indiana.edu/grid/runner/
1> http://iubio.bio.indiana.edu/grid/runner/docs/bix.dtd
1> http://www-igbmc.u-strasbg.fr/BioInfo/ClustalW/
1> http://geta.life.uiuc.edu/~gary/programs/fastDNAml.html
1>
1> end -- VTD_XML_TEST_APP
classNote that each logged routine must start and finish with a paired call to enter_with_args and exit and that the first argument to enter_with_args matches the name of the routine. The log object maintains a logging call stack. A call to enter_with_args pushes a new routine onto the stack and exit pops the entry. The second argument is of type ARRAY [ANY] and is used to log any routine arguments. Routine enter_with_args calls the out function from the universal ancestor class ANY for each of the array items and lists them each on a separate line as argument (1), argument (2) etc.
VTD_XML_TEST_APP
inherit
EL_SUB_APPLICATION
feature -- Basic operations
test_bio_2 (xpath: STRING) is
-- list all url values
local
node_list: EL_XPATH_NODE_CONTEXT_LIST
do
log.enter_with_args ("test_bio_2", << xpath >>)
node_list := bio_info_root_node.context_list (xpath)
from node_list.start until node_list.after loop
log.put_line (node_list.context.string_value)
node_list.forth
end
log.exit
end
feature {NONE} -- Implementation
bio_info_root_node: EL_XPATH_ROOT_NODE_CONTEXT
my_routine is
-- Exception handling routine
local
log_stack_pos: INTEGER
do
log_stack_pos := log.call_stack_count
log.enter ("my_routine")
..
log.exit
rescue
log.restore (log_stack_pos)
..
retry
end
logging.activateIf you wish to use this in any other class you must inherit from EL_MODULE_LOGGING. The following call globally disables all logging except for calls made to the log_or_io object:
logging.deactivate
feature {NONE} -- Constants
Log_filter: ARRAY [TUPLE] is
--
once
Result := <<
["VTD_XML_TEST_APP", "test_bio_1", "test_bio_2", "-test_bio_3"],
["EL_XPATH_ROOT_NODE_CONTEXT", "*"],
-- Output suppressed by prefixing wildcard with hyphen
["EL_XPATH_NODE_CONTEXT_LIST", "-*"],
["EL_XPATH_NODE_CONTEXT", "*"],
["EL_VTD_EXCEPTIONS", "*"]
>>
end
Each tuple in the array consists of the name of the class you wish to
enable logging for, followed either by a list of individual routines or
a wildcard character '*' to indicate that any and all routines
are to
be logged for that class. You can disable logging for any particular
routine by prefixing the name with a hyphen. In the case of a wildcard,
prefixing with a hyphen disables all logging for that
class. The class filter is compared only with the generating class name
so all child classes in a particular inheritance tree must be listed
individually.logging.set_prompt_user_on_exit (true)The logging object is available in the root class or by inheriting EL_MODULE_LOGGING.
By default it is the log output of the main thread that is visible in the console terminal. To change the logging output visible in the console to another thread call redirect_thread_to_console with the thread's index. The index of the main launch thread is 1. Subsequently added threads have indexes of 2, 3, 4 etc. Use function is_valid_console_index to check if the index is valid.execute is
--
do
log_manager.add_visible_thread (Current, "My thread")
..
end
log_manager.redirect_thread_to_console (index)It is this index which is displayed as part of the log output prompt. If you are not sure what the index of the thread is you can obtain it from the thread name with a call like:
my_thread_index := log_manager.thread_index ("My thread")
/<user>/home/.<executable name>/<sub app name>/logsFor example if the executable is named foo and the sub application is bar then for user joeblogs the log directory path is:
/joeblogs/home/.foo/bar/logs
To write an EROS client in a language other than Eiffel requires an understanding of the EROS network protocol and how to correctly format the remote call messages. The rules are few and simple.
To establish a socket connection with the server use a standard network socket class which can send and receive text messages. The client must know in advance what port the server is listening is on. Once the connection is established it is kept alive for the duration of the client session during which any number of remote calls can be made. The client can send either a remote call message or a quit command which causes the server to close the connection and terminate the session.
The EROS server expects the end of all client messages to be delimited with the ctrl-z EOF character (ASCII DEC 26) and replies likewise. There are only 2 types of client message to the server as follows:
A remote routine call request formated as an XML document starting with the standard XML declaration instruction <?xml ..?> and followed by a 'call' processing instruction <?call ..?>
EROS remote call messages are contained in an XML processing instruction and placed immediately after the XML document declaration <?xml ..?>. The syntax of a call message processing instruction from left to right is as follows:
EROS has a small toolkit of 2 classes that make it easy to write proxy classes for remote classes.
The first is EL_NETWORK_STREAM_SOCKET, a descendant of the standard EiffelStudio NETWORK_STREAM_SOCKET with the addition of put_string and read_string routines for sending and receiving complete XML files as a single string. This is made possible by the automatic addition of the EOF delimiter, ctrl-z (DEC 26) in put_string. The procedure read_string reads fixed lengths blocks until it finds one with ctrl-z. This class is used in conjunction with the class EL_REMOTE_PROXY.
Class EL_REMOTE_PROXY is used to create proxy classes
for remote classes. To make a proxy class, create a class named with
the suffix '_PROXY' appended to the name of the remote class and
inheriting class EL_REMOTE_PROXY. So for example the proxy
class for class FOO is FOO_PROXY. If this naming
convention is not adhered to, the correct name of the remote class will
not be derived. There are no
deferred routines to
implement, you just cut and paste the routines you want to call
remotely minus the body, ie. just the routine interfaces. In place of
the body you make a call to the procedure call. This routine
takes care of creating the
remote call processing instruction and inserting it into a XML document
and sending the request to the server. The resulting reply is accessed
as result_string.
When creating a proxy class for a remote class it is useful (but not essential) to define an abstract interface (a deferred class with all deferred routines) for all the remotely callable routines and uses this as a common ancestor for the proxy and remote class. If both the proxy and remote class are linked into the same project, you can use the descendant feature viewer in EiffelStudio to switch between the proxy version and the remote version of a routine.
To implement a proxy routine, copy and paste the routine declaration from the remote class into the proxy class. The body of the remote routine will be replaced by a call to procedure call followed by some code to check if any remote error occurred.
In the case of a proxy function, a successful call will return a string accessible through result_string, an attribute of class EL_REMOTE_PROXY. It must first be converted to an Eiffel type before it can be assigned to the function Result.
Procedure call take 2 arguments as follows:
A tuple listing any Eiffel arguments. (ie. list the arguments in square brackets [a, b, ..]) Allowable argument types include any expanded numeric or string type as well as the special string type EL_EIFFEL_IDENTIFIER which is used to reference a once function in the target object. Any complex type should implement an XML serialization scheme and conform to both EL_SERIALIZEABLE_AS_XML and EL_BUILDABLE_FROM_XML. It will be serialized as an XML document with the call instruction inserted after the XML declaration. If there are no arguments, specify an empty tuple: [].
The following example code illustrates all possible argument types. Class BAR conforms to both EL_SERIALIZEABLE_AS_XML and EL_BUILDABLE_FROM_XML:
class FOOBAR_PROXY
inherit
EL_REMOTE_PROXY
feature -- Access
foo (x: INTEGER; y: DOUBLE; s: STRING color: EL_EIFFEL_IDENTIFIER; bar: BAR): INTEGER is
--
do
call (foo_, [x, y, s, color, bar])
if has_error then
io.put_string ("ERROR: ")
io.put_string (Error_messages [error_code])
io.put_new_line
else
Result := result_string.to_integer
end
end
foo_: STRING is "foo"
end
The following call to foo:
foo (5, 1.2, "game", Color_red, bar)will be translated into an EROS call message something like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?call {GOBO}.foo (5, 1.2, 'game', Color_red, {BAR})?>
<bar>
etc..
</bar>
Note that the bar argument has been replaced with a class place holder representing the deserialized XML document.
foo: FOO is
--
do
call ("foo", [])
if has_error then
create Result.make
else
create Result.make_from_string (result_string)
end
end
class
APPLICATION_ROOT
inherit
EL_MULTI_APPLICATION_ROOT
create
make
feature -- Applications
foo: EL_APPLICATION_LAUNCHER [FOO] is
--
once
create Result.make ("foo", "Description of FOO application")
end
bar: EL_APPLICATION_LAUNCHER [BAR] is
--
once
create Result.make ("bar", "Description of BAR application")
end
feature {NONE} -- Implementation
Application_manifest: ARRAY [EL_APPLICATION_LAUNCHER [EL_SUB_APPLICATION]] is
--
once
Result := << foo, bar >>
end
end
It is possible to use the multi-mode framework to create applications that are self installing, meaning that the application will copy it's own executable into the user bin directory and create either a desktop application launch menu entry or a file context menu launcher which will launch the application passing the currently selected file (or directory) path as an argument. The installation process is invoked by launching the application with the command line switch -install. Use -uninstall to undo the install. Currently it is only implemented for the Gnome desktop but a future release will provide an implementation for KDE, Windows XP and Windows 7. (In fact it may already work for KDE as it is rumored that KDE and Gnome share a common menu standard but I haven't had time to test this conjecture).
Creating a self installing multi-mode application is the same as
creating a standard multi-mode application except that you use the
creation procedure make_install instead of make for
each application container instance of type EL_APPLICATION_LAUNCHER.
The difference between make_install and make is that make_install
takes an extra argument of abstract type EL_APPLICATION_INSTALLER which
is the parent class of the following platform independent installers:
context_menu_script
application_menu
console_application_menu
Nearly all the Eiffel Loop example projects contain multiple applications.
The EROS server project includes three sub-applications related to thread programming:
So far EROS has only been tested on Ubuntu Linux
8.01 using EiffelStudio 6.3. Many of the modules from which EROS has
been constructed have been tested and in some cases developed on
Windows XP but there are still a number of classes which require a
Windows implementation. Support for Windows is planned for a future
release.
All of the C libraries and third party Eiffel libraries have been included with the Eiffel Loop package. Besides a gcc compiler the only other things you need are SCons Ver 1.2 and Python 2.5 or later. Python is required not only for SCons but also for some of the Eiffel Loop modules which use Python libraries via the PEPE module.
sudo python setup.py install --install-scripts=~/binAs well as some extensions to Scons it will also install a script that configures the project environment for EiffelStudio based on a dictionary table in a project file project.py. The script is named: launch_estudio.py
Each example project directory contains in addition to the ecf
files, the two files: project.py and SConstruct. The SConstruct
is a standard file which is the same for all Eiffel Loop projects.
The file project.py contains environment variable
configuration table.
The eiffel_loop.scons.eiffel builder scans the project for C
libraries. If the library does not exist but there is a SConstruct file
in the parent directory then that SConstruct is included in the build.
<variable name="icons" value="graphics/icons"/>The paths in the value attribute are relative to the project directory. Under Unix these graphics directories will be installed in the directory:
<variable name="desktop-icons" value="graphics/desktop-icons"/>
<variable name="images" value="graphics/images"/>
/<user>/home/.local/share/<executable name>The graphics will be installed in one of three sub directories: icons, images and desktop-icons.
Absolute paths for these graphics can be obtained by calling one of the functions icon, image or desktop_menu_icon from class EL_IMAGE_PATH_ROUTINES. Each of these functions takes a file path argument relative to the standard directories: icons, images and desktop-icons. A global instance of EL_IMAGE_PATH_ROUTINES is obtainable from class EL_MODULE_IMAGE_PATH as Image_path.
scons finalize=yes project=server.ecfThen the client:
scons finalize=yes project=eros-test.ecfThe first time the projects are built, some precompiled Eiffel libraries and C libraries dependencies will be built. The precompiled Eiffel libraries especially take some time to build.
scons freeze=yes project=server.ecfThis will build "workbench mode" applications and install any associated graphics.
scons freeze=yes project=eros-test.ecf
launch_estudio.py server.ecfOR
launch_estudio.py eros-test.ecf
Make sure Python module eiffel_loop.scons.eiffel has been
installed from the python-support directory. If not follow the
instructions for installing Eiffel Loop using setup.py.
Make sure you are using SCons Ver 1.2. It may work with an earlier
version but this hasn't been tested.
EIFGENs/classic/F_code/el_server -installThe applications will be installed in the users home bin directory (~/bin) and launch menus created under the menu path Applications/Programming/Eiffel Loop. The server menu launcher has maximum threads set to 3. Once it is installed you can manually change this using the desktop menu editor in the system preferences (Windows or Gnome). The el_server app will also install a menu launcher for a physics lesson animation. The el_eros_test app will also install a menu launcher for a light weight console version of the fourier transformation server.
EIFGENs/eros_test/F_code/el_eros_test -install
To uninstall the menus use the -uninstall command switch.
Eiffel Loop makes a lot of use of a framework for packing multiple applications into one executable. The idea is that you put like applications into one project and select them with a command switch. You can find out what applications are in an excutable by running it without any arguments. A list of command options are printed. Besides the EROS server fourier math application, the el_server executable also contains an animation to demonstrate some other features of the Eiffel Loop thread module. Use the following command to start the EROS fourier math server listening on port 8002 (the default is 8001) and with a maximum of 10 threads to handle requests (the default is 30):
el_server -fourier_math -logging -console -port 8002 -max_threads 10The logging switch turns on the logging. The console switch activates the thread console output switcher. (Recommended) You should now see a small GUI for managing thread log output. Click on the go button and the server is ready to handle requests. Project eros-test.ecf contains a test client for the fourier math demo. It runs some basic tests followed by some randomly generated tests for the number of seconds specified by the the duration argument. The test client is launched with this command:
el_eros_test -test_client -logging -duration 20
Project eros-test.ecf also contains a light weight fourier math test server. It is single threaded and doesn't have any GUI. This is useful for development purposes as it starts up more quickly than the full EROS server which has a GUI. It is recommended that all EROS server applications are developed like this initially before being deployed as a full multi-threaded server. Use ctrl-c to gracefully exit the server. The launch command is:
el_eros_test -test_server -logging
The demo application has 2 remote classes, SIGNAL_MATH and FAST_FOURIER_TRANSFORM_COMPLEX_DOUBLE. The former generates cosine wave form vectors and the latter does fourier transformations. The client has proxies to these classes which it uses to generate a series of random wave forms, print them out and then get the fourier transformations of these wave forms.
EROS is constructed from a number of modules provided in the Eiffel Loop libraries. A summary of the EROS architecture is follows.
The core of the EROS server is built from two classes found in the Eiffel Loop threading module, EL_MANY_TO_ONE_CONSUMER_THREAD and EL_ONE_TO_MANY_THREAD_PRODUCT_QUEUE. These two classes work in tandem to allow the productions of one particular thread to be consumed by an army of consumer threads. Class EL_ONE_TO_MANY_THREAD_PRODUCT_QUEUE takes two generic parameters, the first being the type of the product objects being consumed, and the second being a product consumer conforming to the type EL_MANY_TO_ONE_CONSUMER_THREAD. Class EL_MANY_TO_ONE_CONSUMER_THREAD takes one generic parameter, the product type. Class EL_ONE_TO_MANY_THREAD_PRODUCT_QUEUE manages the product distribution and creation of consumer threads in a 'delegator' thread. The delegation algorithm is as follows: the producer thread places an item on the queue. If there is an available consumer, the delegator assigns the product to that consumer, or else the delegator creates a new consumer thread and assigns it the product. Class EL_ONE_TO_MANY_THREAD_PRODUCT_QUEUE has an attribute specifying the maximum number of consumers that can be created. If the maximum number of consumers has already been reached and all are unavailable, then the delegator waits for one to become free. When each consumer has finished consuming a product it notifies the delegator that it has become available.
The delegator is an instance of class EL_DELEGATING_CONSUMER_THREAD which inherits from class EL_CONSUMER_THREAD. The latter class is designed to be used in a one producer, one consumer construct. The delegating consumer consumes products only by giving it to another thread to consume, hence the name.
For the purposes of the EROS server, a product is defined as a client connection. The consumption of the client connection product is defined as being a session of servicing multiple requests until such time as the client sends a quit message. The client connection consumer is of type EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLING_THREAD inheriting from EL_MANY_TO_ONE_CONSUMER_THREAD. Once the request handling thread has served (consumed) a client connection it is made available again for another connection and relaunched.
Client connections are managed by class EL_REMOTE_CALL_CONNECTION_MANAGER_THREAD. The manager thread polls for new connections every 250 millisecs. This latency is divided by 2 a maximum of 5 times if the connection manager receives a cluster of new connections. The minimum latency is about 16 millisecs. New connections are placed on the session queue for servicing by the class EL_REMOTE_CALL_CLIENT_CONNECTION_QUEUE. This class inherits the class EL_ONE_TO_MANY_THREAD_PRODUCT_QUEUE described in the thread handling section.
Once a connection has been assigned to a thread, the class EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLER is responsible for reading requests from the connection socket, parsing the requests and sending replies. It takes as a creation argument, an array of factory objects for remotely accessible objects. The principle routine serve takes a stream socket as a parameter, and keeps replying to requests until such time as it receives a quit command. If it finds a message starting with an XML declaration, it assumes that there is a processing instruction containing a request immediately after the declaration.
The test client demo application also contains an example of a simple test server as a sub application. This single threaded test server* makes direct use of EL_REMOTE_ROUTINE_CALL_REQUEST_HANDLER without the overhead of thread management or controller GUI. Using this code is recommended for developing and debugging a new EROS application.
(* The project is still compiled as multi-threaded)EROS makes use of the Eiffel Loop parsing and lexing module to parse the remote call request contained in the XML processing instruction. This module is also used to implement the templating (or text substitution) mini language Evolicity used for textual data serialization. The complete call request parser is implemented in class EL_ROUTINE_CALL_REQUEST_PARSER shown below. The grammar is fairly self explanatory with a small amount of explanation of the constructs all_of and one_of. The all_of construct returns a text match if each pattern in the list returns a match in succession. The one_of construct starts of with the first pattern in the list and works its way down until either one of the patterns matches or the end of the list is reached. It's a bit like a series of nested or else constructs in Eiffel: if a or else (b or else ..) then. It returns on encountering the first match.
The infix operator #with is used to assign a match handling procedure to a particular textual sub pattern. It works by calling procedure set_action_on_match and returns the current pattern object. (It does shoot a hole in the CQS thing but makes life easier) If the sub pattern is successfully matched within the context of the complete pattern being fully matched, then the agent is called with the matching text as an argument. The agents are called in the same order as the matches occur. In the case of a pattern with nested sub patterns, the agent for the parent pattern is called before the child pattern agents. This is a simplified account of the match handling rules but sufficient to understand the EROS request parser.
The root of the
grammar is found in function create_pattern which
starts with the keyword call.
The request parser makes use of some patterns defined in the
inherited class EL_EIFFEL_PATTERN_FACTORY
. The TP suffix used to name the lexing classes, stands
for Textual Pattern.
class
EL_ROUTINE_CALL_REQUEST_PARSER
inherit
EL_LEXICAL_PARSER
rename
make as make_parser,
match_full as parse
redefine
reset_parser
end
EL_EIFFEL_PATTERN_FACTORY
export
{NONE} all
end
create
make
feature {NONE} -- Initialization
make is
--
do
create routine_name.make_empty
create argument_list.make (3)
make_parser
end
feature -- Access
routine_name: STRING
class_name: STRING
argument_list: ARRAYED_LIST [STRING]
feature {NONE} -- Syntax grammar
create_pattern: EL_MATCH_ALL_IN_LIST_TP is
--
do
Result := all_of (<<
string_literal ("call"),
white_space,
class_object_place_holder #with agent on_class_name,
character_literal ('.'),
c_identifier #with agent on_routine_name,
maybe_white_space,
optional (argument_list_pattern)
>>)
end
argument_list_pattern: EL_MATCH_ALL_IN_LIST_TP is
--
do
Result := all_of (<<
character_literal ('('),
maybe_white_space,
argument,
while_not_pattern_1_repeat_pattern_2 (
right_bracket,
-- pattern 2
all_of (<<
maybe_white_space,
character_literal (','),
maybe_white_space,
argument
>>)
)
>>)
end
argument: EL_FIRST_MATCH_IN_LIST_TP is
--
do
Result := one_of ( <<
class_object_place_holder #with agent on_argument,
singley_quoted_string #with agent on_argument,
double_constant #with agent on_numeric_argument,
integer_constant #with agent on_numeric_argument,
boolean_constant #with agent on_boolean_argument,
identifier #with agent on_identifier_argument
>> )
end
class_object_place_holder: EL_MATCH_ALL_IN_LIST_TP is
--
do
Result := all_of ( <<
character_literal ('{'),
class_identifier,
character_literal ('}')
>> )
end
boolean_constant: EL_FIRST_MATCH_IN_LIST_TP is
--
do
Result := one_of (<<
string_literal ("true"),
string_literal ("false")
>>)
end
singley_quoted_string: EL_MATCH_ALL_IN_LIST_TP is
--
do
Result := all_of ( <<
single_quote,
while_not_pattern_1_repeat_pattern_2 (single_quote, any_character)
>> )
end
single_quote: EL_LITERAL_CHAR_TP is
--
do
create Result.make_from_character ('%'')
end
right_bracket: EL_LITERAL_CHAR_TP is
--
do
create Result.make_from_character (')')
end
feature {NONE} -- Parsing match events
on_class_name (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
class_name := matched_text
class_name.remove_head (1)
class_name.remove_tail (1)
end
on_routine_name (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
routine_name := matched_text
end
on_argument (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
argument_list.extend (matched_text)
end
on_numeric_argument (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
argument_list.extend (once "(" + matched_text.string + once ")")
end
on_boolean_argument (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
argument_list.extend (once "<" + matched_text.string + once ">")
end
on_identifier_argument (matched_text: EL_SOURCE_TEXT_VIEW) is
--
do
argument_list.extend (once "[" + matched_text.string + once "]")
end
feature {NONE} -- Implementation
reset_parser is
--
do
Precursor
routine_name.clear_all
argument_list.wipe_out
end
end