Translate VisualBasic expressions to JavaScript expressions dynamically

Viewed 70

I want to translate expressions (conditions) written in VisualBasic style to JavaScript code. Because I haven't found a library that does this, I'll have to do it myself. Or does anyone know a project that I could use?

My requirement: I get a string from a file with an expression written in VB. It is not a full VB code, but only single VB functions like IF, TRIM, LEFT, etc. and some other functions like RECORD_NR() (I think this is not a native function of vb). I now want to translate this string into JavaScript (can also come as a string, as I write the JavaScript code into a file afterwards). One of the problems is that all characters in quotes must not be handled functionally.

Example expression:

IF(RECORD_NR() = 1, "NR,1",IF(RECORD_NR() = 2, "NR,2", "NR3"))

Possible result:

var result = "";
if(record.index == 1){
    result = "NR,1";
} else {
    if(record.index == 2){
        result = "NR,2";
    } else {
        result = "NR3";
    }
}

The following is the approach I am currently using and I am not sure if it will get me to my goal:

  1. Search areas which are in quotes and store position informations (start and end) in json object (jsonQuotes).
  2. Search for ranges in brackets and save the positions (start and end) in a json object (jsonBrackets). Here I will exclude ranges in quotes based on jsonQuotes.
  3. Search keywords (function names) which are not in quotes and add found keyword to json object jsonBrackets (so I know which function belongs to the bracketed area).

At this point I have the following json object:

[
    {
        "ID": 1,
        "start": 2,
        "end": 60,
        "fnc": "IF"
    },
    {
        "ID": 2,
        "start": 12,
        "end": 13,
        "fnc": "RECORD_NR"
    },
    {
        "ID": 3,
        "start": 29,
        "end": 59,
        "fnc": "IF"
    },
    {
        "ID": 4,
        "start": 39,
        "end": 40,
        "fnc": "RECORD_NR"
    }
]

And now I am struggling. I want to sort the json object or nest it like:

[
    {
        "ID": 1,
        "start": 2,
        "end": 60,
        "fnc": "IF",
        "contains":[
            {
                "ID": 2,
                "start": 12,
                "end": 13,
                "fnc": "RECORD_NR",
                "contains":[]
            },
            {
                "ID": 3,
                "start": 29,
                "end": 59,
                "fnc": "IF",
                "contains":[
                    {
                        "ID": 4,
                        "start": 39,
                        "end": 40,
                        "fnc": "RECORD_NR",
                        "contains":[]
                    }
                ]
            }
        ]
    }
]

Can someone help me to achieve that or better does anyone know a better way to get the desired result (maybe a different, better approach)?

2 Answers

According to the tasks that need to be processed, all you need is a compiler. That need to convert the vb program to ast first, then traverse the ast, and convert the ast to the js code, just like babel does. The following link is an example In addition, here is a third-party library that can be used. https://github.com/zaach/jison

Or use a hacky way, all functions return an object property, the property sets a setter, when assigning a value, the value is assigned to the specified field, and finally a js function named IF will process each parameter value .

You can try using some PEG parser generator like PEG.js because it's easier to write grammars in PEG.

Here is a sample PEG.js grammar which implements an expression parser that can parse a subset of some VB expressions (not tested too much):

// All operators in VBA are left-associative (incl. power)
// Precedence from https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/operator-precedence

Expr = Logical1

Logical1 = left:Logical2 t:(_ op:OperOr right:Logical2      { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Logical2 = left:Logical3 t:(_ op:'And'i right:Logical3      { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Logical3 = op:'Not'i right:Logical3                         { return { kind: "unop", op, right } }
    / Comp

Comp = left:Arith1 t:(_ op:OperComp right:Arith1            { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }

Arith1 = left:Arith2 t:(_ op:[&] right:Arith2               { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Arith2 = left:Arith3 t:(_ op:[+-] right:Arith3              { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Arith3 = left:Arith4 t:(_ op:'Mod'i right:Arith4            { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Arith4 = left:Arith5 t:(_ op:[\\] right:Arith5              { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Arith5 = left:Arith6 t:(_ op:[*/] right:Arith6              { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }
    
Arith6 = op:[+-] !(_ Number) right:Arith6                   { return { kind: "unop", op, right } }
    / Arith7
    
Arith7 = left:Primary t:(_ op:[\^] right:Primary            { return { op, right } }
    )* { return t.reduce((a, e) =>                          { return { kind: "binop", left:a, op:e.op, right:e.right } }, left) }

Primary = _ id:Ident _ '(' params:Params _ ')'              { return { kind: "call", id, params } }
    / _ id:Ident                                            { return { kind: "var", id } }
    / _ n:Number                                            { return n }
    / _ s:String                                            { return s }
    / _ '(' e:Expr _ ')'                                    { return e }

Params = p:Expr _ ',' r:Params                              { return [p].concat(r) }
    / e:Expr                                                { return [e] }
    / _                                                     { return [] }

// Lexical rules

OperOr = 'Or'i / 'Xor'i / 'Eqv'i / 'Imp'i
OperComp = '<=' / '=>' / '<>' / [<>=] / 'Like'i / 'Is'i
Ident = [A-Za-z_][A-Za-z0-9_]*                              { return text() }
String = '"' s:('""' / [^"])* '"'                           { return s.join("").replace('""', '"') }
Number = ([+-] _)? Int ('.' Int?)? ([Ee] [+-]? Int)?        { return Number(text()) }
Int = [0-9]+
_ = [ \t\r\n]*

This will parse your IF(RECORD_NR() = -1, "NR,1",IF(RECORD_NR() = 2, "NR,2", "NR3")) into a parse tree like this:

{
   "kind": "call",
   "id": "IF",
   "params": [
      {
         "kind": "binop",
         "left": {
            "kind": "call",
            "id": "RECORD_NR",
            "params": []
         },
         "op": "=",
         "right": -1
      },
      "NR,1",
      {
         "kind": "call",
         "id": "IF",
         "params": [
            {
               "kind": "binop",
               "left": {
                  "kind": "call",
                  "id": "RECORD_NR",
                  "params": []
               },
               "op": "=",
               "right": 2
            },
            "NR,2",
            "NR3"
         ]
      }
   ]
}

Traversing such parse tree and emitting appropriate JS code to evaluate the expression is up to you.

You can fiddle with the grammar above online in the PEG.js playground at https://pegjs.org/online

Related