B4A Library MteEval - B4X Expression Compiler and Eval Library (Open Source)

B4X Expression Compiler and Eval Library

MteEVAL is a library for compiling and evaluating expressions at runtime. Expressions are converted to bytecode and then executed on demand with a simple virtual machine.

There are five editions of the library:
  • Android (B4A)
  • iOS (B4i)
  • Java (B4J)
  • JavaS2 (B4A/B4J)
  • .NET (C#)
JavaS2 is our stage 2 performance edition of the library written in native Java.

Application

Creating expressions at runtime is a powerful tool allowing calculations and program flow to be modified after installation, which otherwise would require a physical update or a custom build of an application. For example, any application designed to manage a sales compensation plan could benefit from runtime expressions, where the end-user may want to customize the plan's formulas by team members, product mixes and sales goals.

Codeblocks

MteEVAL implements a single class named Codeblock. MteEval's codeblock adopts the expression syntax from the venerable 1990's xBase compiler Clipper 5 where the construct began. Codeblocks start with an open brace, followed by an optional parameter list between pipes, then the expression, and end with a closing brace.

{|<parameters>|<expression>}

Operator support

The library supports C/Java style operators along side a growing list of B4X native functions.
  • Math operators: +-*/%
  • Relational: > < >= <= != ==
  • Logical: || && !
  • Bitwise: << >> & ^ |
  • Assignment: =
  • Constants: cPI, cE
  • Functions: abs(), ceil(), floor(), iif(), if(), min(), max(), sqrt(), power(),round()
  • Trig Functions: acos(), acosd(), asin(), asind(), atan(), atand(), cos(), cosd(), sin(), sind(), tan(), tand()
Examples

You only need to compile a Codeblock once. Once compiled you can evaluate it as many times as needed, all while supplying different arguments. All arguments and the return value are type Double.

Example 1: Codeblock without parameters
B4X:
Dim cb as Codeblock
Dim Result as Double
cb.Initialize
cb.Compile( "{||5 + 3}" )
Result = cb.Eval           'Result=8

Example 2: Codeblock with parameters
B4X:
Dim cb as Codeblock
Dim Area as Double
cb.Initialize
cb.Compile( "{|length,width|length*width}" )
Area = cb.Eval2( Array( 3, 17 ) )    'Area=51

When evaluating with parameters, use the Eval2 method.

Example 3: Compile, eval and repeat
B4X:
Dim cb as Codeblock
Dim Commission1, Commission2, Commission3 As Double
cb.Initialize
cb.Compile( "{|sales,r1,r2| r1*sales + iif( sales > 100000, (sales-100000)*r2, 0 ) }" )
Commission1 = cb.Eval2( Array( 152000, .08, .05 ) )    'Commission1=14760
Commission2 = cb.Eval2( Array( 186100, .08, .07 ) )    'Commission2=20915
Commission3 = cb.Eval2( Array( 320000, .08, .05 ) )    'Commission3=36600

Linking to your project
  • To use the Android or Java editions, add the .JAR and .XML files to your Additional Libraries folder and check the MteEVAL library in the Libraries Manager of the IDE.
  • For iOS, copy the modules Codeblock.bas, Codegen.bas, PCODE.bas, and Run.bas to your project folder or place them in the Shared Modules folder. Then add the modules to the project through the IDE.
Demo

Library and demo attached.

Source

Source code is maintained here:
https://github.com/macthomasengineering

Follow this link to track the status of the library at Waffle.io
https://waffle.io/macthomasengineering/mteeval-b4x-library
 

Attachments

  • mteevalS2_b4a_v105.zip
    40.5 KB · Views: 296
  • mteeval_b4a_v106.zip
    46 KB · Views: 303
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
If you are going to do this what about making it equally easy to add constants?

Regards...

Will do. I'll add support for user functions and constants in the 1.07 1.08 release.

** Important ** In version 1.06, I renumbered the Pcodes and grouped them in Run.bas to improve B4X select/case performance. So you'll need to update the Pcodes assigned to your extensions and add them to the last group in Run.bas.

Also, no need to change the DumpCode() pcode ordering as decompiling isn't performance sensitive.

1_mteeval_run_func_group_zpsegejcvpz.jpg
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
Hi, I have just successfully integrated your 1.04 code into a scripting "language" (more like a mini subset pig FORTRAN IV) I have developed for my app.

I accidentally discovered the following eccentricities:
B4X:
    DoTest(  40, "this works", "{|i,j|iif(i==j,1,0)}",  Array (0,1), 0 )
DoTest(  41, "this works", "{|i,j|iif((i)==j,1,0)}",  Array (0,1), 0 )
DoTest(  42, "this works - what does it mean?", "{|i,j|iif(i=j,1,0)}",  Array (0,1), 0 )
DoTest(  43, "this produces 'missing comma' error - not really on the ball", "{|i,j|iif((i)=j,1,0)}",  Array (0,1), 0 )
I actually initially spent several hours trying to work out what I was doing wrong with something equivalent to test 43 - I eventually realised it was == not = - duh as Homer would say.

It took me so long because I was getting "missing comma" - this seems pretty wide of the mark.

But I also discovered test 42 doesn't bomb - what does this mean?

I'm still a happy puppy...
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
Will do. I'll add support for user functions and constants in the 1.07 release.
I think I will sit on 1.04 until you have finished your current flurry of activity - any ETA on 1.07?

Cheers...
 

stanmiller

Active Member
Licensed User
Longtime User
Hi, I have just successfully integrated your 1.04 code into a scripting "language" (more like a mini subset pig FORTRAN IV) I have developed for my app.

Oh wow! This bit might merit a separate thread... Did you implement the scripting part as a classic interpreter or are you converting to pcode?
 

stanmiller

Active Member
Licensed User
Longtime User
B4X:
DoTest(  42, "this works - what does it mean?", "{|i,j|iif(i=j,1,0)}",  Array (0,1), 0 )

There are two features in MteEVAL not talked about in the overview but are found in the test cases at GitHub.

1. Support for the assignment operator (=) in expressions.
2. Expression lists.

Your test #42 is using the assignment operator in a conditional expression which is permitted but typically would generate a warning with conventional compilers.

The code is behaving correctly, though not necessary with the expected result.

This is a good thread about the situation at Stackoverflow.
http://stackoverflow.com/questions/151850/why-would-you-use-an-assignment-in-a-condition

Here's the codeblock.Decompile() with comments.
B4X:
------------------------
-- Header --
Parameters=2
-- Code --
1:     loadv   ax, varmem[1]   ' IIF( Load J into AX
3:     storev  varmem[0], ax   ' Store AX into I
5:     jumpf   11              ' AX == 0? jump to instruction #11,
7:     loadc   ax, 1           ' THEN: Load 1 into AX
9:     jump    13              ' Jump to instruction #13,
11:    loadc   ax, 0           ' ELSE: Load 0 into AX )
13:    end                     ' Return value in AX
------------------------

When iif() tests the condition, i <> 0 so it defaults to the true case, instruction #7.

Normally the IDE or compiler will warn you of such things. I've added a setting to version 1.07 to allow you to permit or deny the assignment operator in conditional expressions. By default it is set to False.
 
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
B4X:
DoTest(  43, "this produces 'missing comma' error - not really on the ball", "{|i,j|iif((i)=j,1,0)}",  Array (0,1), 0 )

I agree. The missing comma error is misleading. It should say "expected comma here, but there is an equal sign instead?"

1_illegal_assignment_zpsex1fswjy.jpg


This is an illegal assignment as there is no "L-value" to assign j to. The error is now trapped and reported as an "illegal assignment" in version 1.07.

Thank you for making the library better!
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
Oh wow! This bit might merit a separate thread...
Don't get too carried away - I said "mini subset pig FORTRAN IV" and meant it:
  • No concept of character handling beyond ability to construct a string from literals and formatted numbers - even then in a remarkably crude way.
  • If ... statement without concept of End If - just allows you to conditionally execute next statement (which is a new low).
  • No concept of For/Next (might need to revisit this).
  • No user definable variable names - there are a number of predefined variables for dialog management etc and a single open ended "scratch" array called "value".
  • Goto is only means of branching:) - aside from events due to user input.
  • And these are just the "features" I can recall off the top of my head.
But for all this, it does what I want which is to provide a ultra simple means of creating external flat file scripts to create a consistent dialog from a palette of elements, wait for user input and respond - possibly launching an Activity, then massage the results.
Did you implement the scripting part as a classic interpreter or are you converting to pcode?
Just a real simple interpreter - its essentially a huge If/Else/Select Case tree.

On paper it looks hellishly inefficient but it is not intended for inverting large matrices.
 
Last edited:

imbault

Well-Known Member
Licensed User
Longtime User
@stanmiller, is that possible (in the future) your library could work with Strings ? and string evaluations... it should be great
 

JackKirk

Well-Known Member
Licensed User
Longtime User
Your test #42 is using the assignment operator in a conditional expression which is permitted but typically would generate a warning with conventional compilers.
Hmmm, I need to constipate on this a while - it could prove confusing to a user generating one of my scripts - I will see if I can come up with a definitive example in the context of my scripts.

EDIT - OK I constipated - more correctly I dumped your 1.07 iOS Codeblock.bas, Codegen.bas, PCODE.bas and Run.bas into my B4A test rig and ran it.

All worked without a hitch - pretty impressive in itself in my view (i.e. good code on your part).

DoTest( 42, "this works - what does it mean?", "{|i,j|iif(i=j,1,0)}", Array (0,1), 0 )

Now produces:

Compile error=27
Error Description: Assignment in conditional not permitted. To enable, set Codeblock.AllowConditionalAssignment=True
Error Detail: iif(i= <e27>

Which is excellent - I like the Codeblock.AllowConditionalAssignment switch and will leave it at False.

DoTest( 43, "this produces 'missing comma' error - not really on the ball", "{|i,j|iif((i)=j,1,0)}", Array (0,1), 0 )

Now produces:

Compile error=26
Error Description: Illegal assignment.
Error Detail: iif((i)= <e26>

Which is also excellent.

I think that means case closed on these 2 tests.

Thanks Todd...
 
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
I think I will sit on 1.04 until you have finished your current flurry of activity - any ETA on 1.07 1.08?
Cheers...

I've been trying to figure out a clever way not to use "callsub()" and the overhead it brings. But it looks like callsub is the only way possible to dynamically bind and call functions outside the lib.

It won't be long for 1.08. A week or so...
 

stanmiller

Active Member
Licensed User
Longtime User
@stanmiller, is that possible (in the future) your library could work with Strings ? and string evaluations... it should be great

Yes. Now that the code and data are stored separately, it's not much more to add another table to hold strings. I could add new instructions (LOADS, PUSHS, STORES, etc.) to operate on the strings as well as would need to update the regex definition(s) to tokenize strings in expressions.

Do you have a specific use case I could target?
 
Top