Saturday, December 12, 2009

Herramienta matemática con RegEx y JavaScript


Post original "RegEx and JavaScript Mathematical Tool".

Si me conocen...ya saben que amo los nuevos lenguajes de programación y las herramientas. Aunque las Expresiones Regulares y el JavaScript no son exactamente nuevos, no han sido explotados en ABAP, es por eso que quiero que este blog muestre todo lo que podemos lograr utilizándo cualquiera de estas dos adiciones al lenguaje.

La historia en resumen, es así...Estaba revisándo mi Google Reader como todas la mañanas, cuando vi este excelente y muy gracioso comic de XKCD...


Supe que era la hora de intentar convertirme en un heroe del RegEx...

Debo admitir que el RegEx no es fácil...a veces es complejo y avaro...pero creánme...llegas a amarlo (Ok...no todo el mundo lo ama...)

Así que, supe que era hora de que construyera una pequeña herramienta utilizándo mis nuevas habilidades. Así que construí una pequeña herramienta matemática, utilizándo tanto RegEx como JavaScript...y lo gracioso aquí es que mientras me rompía la cabeza con RegEx, recordé que utilizándo JavaScript tenía el trabajo prácticamente hecho...pero como soy un Geek...no podía simplemente tomar el camino fácil...seguí luchando hasta que mi RegEx funcionó, pero dejé mi JavaScript como una manera de decir..."Hay más de una manera de hacer que las cosas funcionen...".

Dejenme mostrarles algunas imagenes primero, y luego les muestro el código fuente...


Podemos pasar una expresión muy simple...


O una expresión muy compleja...


Y probarlas ya sea con RegEx o con JavaScript...


Ahora el código fuente...


*&-----------------------------------------------------------*
*& Report ZDUMMY_SANDBOX *
*&-----------------------------------------------------------*
*& Sandbox application only for testing porpouses. *
*&-----------------------------------------------------------*
REPORT zdummy_sandbox.

*INTERNAL TABLES
DATA: message TYPE string,
t_results TYPE match_result_tab,
t_precedence TYPE match_result_tab,
t_operations TYPE match_result_tab.

*VARIABLES
DATA: g_number TYPE string,
g_sum TYPE i,
g_sum_text TYPE string,
g_operator TYPE string,
g_string TYPE string,
source TYPE string,
return_value TYPE string,
js_processor TYPE REF TO cl_java_script.

*CONSTANTS
CONSTANTS: c_operator TYPE string VALUE '+*/-%'.

*FIELD-SYMBOLS
FIELD-SYMBOLS: <fs_results> LIKE LINE OF t_results,
<fs_precedence> LIKE LINE OF t_precedence,
<fs_operations> LIKE LINE OF t_operations.

*SELECTION-SCREEN
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME.
PARAMETERS:
p_msg TYPE string OBLIGATORY,
p_js RADIOBUTTON GROUP sa DEFAULT 'X',
p_re RADIOBUTTON GROUP sa.
SELECTION-SCREEN END OF BLOCK b01.

*START-OF-SELECTION
START-OF-SELECTION.

message = p_msg.

IF p_re EQ 'X'.
*REGULAR EXPRESSIONS
WHILE sy-subrc EQ 0.

FIND REGEX '[^\+\-]\*.*' IN message RESULTS t_precedence.
IF sy-subrc EQ 0.
READ TABLE t_precedence INDEX 1 ASSIGNING <fs_precedence>.
g_string = message+<fs_precedence>-offset(<fs_precedence>-length).
CONCATENATE '(' g_string ')' INTO g_string.
REPLACE REGEX '[^\+\-]\*.*' IN message WITH g_string.
CONDENSE g_string.
ENDIF.

FIND REGEX '\(([0-9]{0,9}|[\* \+ \- \/])+\)' IN message RESULTS t_results.
READ TABLE t_results INDEX 1 ASSIGNING <fs_results>.
IF sy-subrc NE 0 AND <fs_results> IS NOT ASSIGNED.
EXIT.
ENDIF.
g_string = message+<fs_results>-offset(<fs_results>-length).
CONDENSE g_string.
FIND ALL OCCURRENCES OF REGEX '([\(]|[0-9]{0,9}|[\* \+ \- \/]|[\)])'
IN g_string RESULTS t_operations.

LOOP AT t_operations ASSIGNING <fs_operations>.
g_number = g_string+<fs_operations>-offset(<fs_operations>-length).
IF g_number EQ '(' OR g_number EQ ')'.
CONTINUE.
ENDIF.
IF g_sum EQ space.
g_sum = g_number.
ENDIF.
IF g_number CA c_operator.
g_operator = g_number.
CONTINUE.
ENDIF.
CASE g_operator.
WHEN '+'.
g_sum = g_sum + g_number.
CLEAR g_operator.
WHEN '-'.
g_sum = g_sum - g_number.
CLEAR g_operator.
WHEN '*'.
g_sum = g_sum * g_number.
CLEAR g_operator.
WHEN '/'.
g_sum = g_sum / g_number.
CLEAR g_operator.
WHEN '%'.
g_sum = g_sum MOD g_number.
CLEAR g_operator.
ENDCASE.
ENDLOOP.

g_sum_text = g_sum.
REPLACE REGEX '\(([0-9]{0,9}|[\* \+ \- \/])+\)' IN message
WITH g_sum_text.

FIND REGEX '[^\+\-]\*.*' IN message RESULTS t_precedence.
IF sy-subrc EQ 0.
READ TABLE t_precedence INDEX 1 ASSIGNING <fs_precedence>.
g_string = message+<fs_precedence>-offset(<fs_precedence>-length).
CONCATENATE '(' g_string ')' INTO g_string.
REPLACE REGEX '[^\+\-]\*.*' IN message WITH g_string.
CONDENSE g_string.
ENDIF.

CLEAR: g_sum,g_operator,g_number.
ENDWHILE.

FIND ALL OCCURRENCES OF REGEX '([\(]|[0-9]{0,9}|[\* \+ \- \/]|[\)])'
IN message RESULTS t_operations.
LOOP AT t_operations ASSIGNING <fs_operations>.
g_number = message+<fs_operations>-offset(<fs_operations>-length).
IF g_number EQ '(' OR g_number EQ ')'.
CONTINUE.
ENDIF.
IF g_sum EQ space.
g_sum = g_number.
ENDIF.
IF g_number CA c_operator.
g_operator = g_number.
CONTINUE.
ENDIF.
CASE g_operator.
WHEN '+'.
g_sum = g_sum + g_number.
CLEAR g_operator.
WHEN '-'.
g_sum = g_sum - g_number.
CLEAR g_operator.
WHEN '*'.
g_sum = g_sum * g_number.
CLEAR g_operator.
WHEN '/'.
g_sum = g_sum / g_number.
CLEAR g_operator.
WHEN '%'.
g_sum = g_sum MOD g_number.
CLEAR g_operator.
ENDCASE.
ENDLOOP.

WRITE:/ 'The result is:', g_sum.

ELSE.
*JAVASCRIPT
js_processor = cl_java_script=>create( ).
CONCATENATE
'var string = ' message ';'
'function Set_String() '
' { string = eval(string); '
' } '
'Set_String(); '
'string; '
INTO source SEPARATED BY cl_abap_char_utilities=>cr_lf.

return_value = js_processor->evaluate( source ).

WRITE:/ 'The result is:', return_value.
ENDIF.

Utilicé RegEx para extraer las operaciones dentro de los parentesis y para construir la precedencia de operadores.

Si encuentran algún bug (Realmente espero que no), no duden en decirmelo -:)

Si quieren más información sobre RegEx por favor lean esto en el SCN.

ABAP Geek 14 - Regular Expressions Made Easy

Express Yourself Regularly with SAP NetWeaver 7.0, Part 1

Express Yourself Regularly with SAP NetWeaver 7.0, Part 2

Express Yourself Even More Regularly

Saludos,

Blag.

6 comments:

Anonymous said...

Buenas Blag! enhorabuena una vez más por tus investigaciones de nuevas herramientas xD. En este caso me dio por probar el report y he visto que con el método RegEx no evalua bien la prioridad de operaciones, dándole más prioridad a la suma que a al producto. Simplemente hay que probar con una expresión como 5+2*25 para ver que da 175 en lugar de 55 :S.

Es curioso ver también el rendimiento de ambos tipos desde la SE30.

Un saludo máquina.

Alvaro "Blag" Tejada Galindo said...

No me habia dado cuenta de que el RegEx no respetaba la prioridad de operaciones...gracias por hacermelo notar -;)

Saludos,

Blag.

Anonymous said...

Hola! no funciona con valores negativos, muestra error.

Alvaro "Blag" Tejada Galindo said...

Es verdad, con valores negativos se cae...aunque...cuando hice este codigo era porque estaba trabajando en un proyecto de HR y se necesitaba hacer una interfaz de nomina...entonces algo asi como Nuevo Pago = Sueldo_Neto + Bonificacion - Descuento. En ese caso, no existen los valores negativos, puesto que son formulas a aplicar...por eso nunca me preocupe por los valores negativos...quizas algun dia haga la correccion -:)

Saludos,

Blag.

Raúl said...

Yo le he probado y hasta ahora funciona bien.

Gracias por el aporte! =D

Alvaro "Blag" Tejada Galindo said...

Gracias Raul! -:D

Saludos,

Blag.