├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── basic.yaml ├── basic_conditionals.yaml ├── basic_raw.yaml ├── basic_ts.yaml ├── flows.yaml ├── hotel_booking.yaml ├── snippet1.json └── stocks.yaml ├── lib ├── action.dart ├── api.dart ├── errors.dart ├── expression.dart ├── extensions.dart ├── invokables │ ├── context.dart │ ├── invokable.dart │ ├── invokablecommons.dart │ ├── invokablecontroller.dart │ ├── invokablemath.dart │ ├── invokableprimitives.dart │ ├── invokableregexp.dart │ ├── invokabletext.dart │ └── invokabletextformfield.dart ├── layout.dart ├── main.dart ├── parser │ ├── ast.dart │ ├── find_bindables.dart │ └── newjs_interpreter.dart └── view.dart ├── pubspec.yaml ├── test ├── new_interpreter_tests.dart ├── primitives_test.dart └── widget_test.dart └── test_resources ├── arrayaccesstest.json ├── arraymaptest.json ├── es121.json ├── expression.json ├── getstringvalue.json ├── highcharts1.json ├── ifstatement.json ├── jsonobject.json ├── jsonpath.json ├── jsonpathints.json ├── listsortunique.json ├── maptest.json ├── morearrays.json ├── notnulltest.json ├── nulltest.json ├── primitives.json ├── propsthroughquotes.json ├── returnexpression.json ├── returnidentifier.json ├── ternary.json ├── varArrDecl.json └── variabledecl.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | /ios/ 48 | /build/ 49 | /android/ 50 | /.idea/ 51 | /.dart_tool/ 52 | /analysis_options.yaml 53 | /.metadata 54 | /pubspec.lock 55 | /web/ 56 | windows 57 | local.properties 58 | .gradle 59 | macos 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Version 1.0.0+1 2 | - Initial Package 3 | - Support for javascript (ES5) syntax 4 | - Supports most of the Javascript primitive types such as string, number, arrays and maps. 5 | - Supports declaring javascript functions and calling them 6 | - Does not support declaring classes or interfaces or instantiating them. 7 | - Does not support import or require 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Ensemble Technologies Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | (This repo is deprecated. Please use the monorepo [Ensemble](https://github.com/EnsembleUI/ensemble) instead). 2 | 3 | 4 | This is a javascript [ES5](https://www.geeksforgeeks.org/difference-between-es5-and-es6/) parser and interpreter written entirely in dart. 5 | 6 | ### What it is 7 | - Primary use case is to let users type in simple js that you want to execute inline. This should be not used as a general replacement for dart in flutter 8 | - Runs in the same process as your Dart/Flutter code so no need to use the browser's javascript engine. As a result, this is fast. 9 | - Unlike react native, doesn't require any bridge or have memory issues 10 | - Supports most common use cases right now such as functions, lists, all primitive types (string, number, arrays, dates) etc. 11 | - Highly extensible. The context object could be json or any dart object enhanced with the Invokable mixin (see below) 12 | 13 | ### How to use 14 | 15 | - in your pubspec.yaml, add the following line under dependencies - 16 | ``` 17 | ensemble_ts_interpreter: 18 | git: 19 | url: https://github.com/EnsembleUI/ensemble_ts_interpreter.git 20 | ref: master 21 | ``` 22 | - run ```flutter pub upgrade``` 23 | - Simply call the ```JSInterpreter``` with the code you want to evaluate while passing it the context. 24 | 25 | ```JSInterpreter.fromCode(code, context).evaluate();``` 26 | 27 | ```context``` is the key object here. You can pass json as context (see examples below) or pass an instance of [Invokable](https://github.com/EnsembleUI/ensemble_ts_interpreter/blob/master/lib/invokables/invokable.dart) which could be any Dart object. 28 | 29 | ### Examples 30 | All the examples are in the unit test suite - [new_interpreter_tests](https://github.com/EnsembleUI/ensemble_ts_interpreter/blob/master/test/new_interpreter_tests.dart) 31 | 32 | Listing some here. 33 | 34 | #### Filter a list 35 | 36 | ``` 37 | Map context = { 38 | 'items': ['one', 'two', 'three'], 39 | 'nested': [ 40 | {'id': 1, 'label': 'eggs', 'type': ''}, 41 | {'id': 2, 'label': 'strawberry', 'type': 'fruit'}, 42 | {'id': 3, 'label': 'nut'} 43 | ] 44 | }; 45 | 46 | String code = """ 47 | var flatList = items.filter(function(e) { 48 | return e != 'two'; 49 | }); 50 | 51 | var nestedList = nested.filter(function(e) { 52 | return e['type'] == 'fruit' 53 | }); 54 | """; 55 | 56 | JSInterpreter.fromCode(code, context).evaluate(); 57 | ``` 58 | #### Different String functions 59 | 60 | ``` 61 | Map context = initContext(); 62 | 63 | String code = """ 64 | var arr = ['a','b','c','d']; 65 | var b = arr.at(1); 66 | var arr2 = arr.concat(['e','f']); 67 | var f = arr2.find(function (element) { 68 | var rtn = ( element == 'f' )? true : false; 69 | return rtn; 70 | }); 71 | var includes = arr2.includes('e'); 72 | var str = arr.join(); 73 | var str2 = arr.join('-'); 74 | var str3 = arr.join(''); 75 | var last = arr2.pop(); 76 | var nums = [1,2,3,4,5]; 77 | var sum = nums.reduce(function (value, element) { 78 | return value + element; 79 | }); 80 | var reversed = arr.reverse(); 81 | """; 82 | 83 | JSInterpreter.fromCode(code, context).evaluate(); 84 | ``` 85 | 86 | #### Function Declaration and then calling the functions 87 | ``` 88 | test('functiondeclarationtext', () async { 89 | String codeToEvaluate = """ 90 | var i = 0; 91 | var users = [{'name':'Khurram'},{'name':'Mahmood'}]; 92 | updateSalary(users,noArgFunction()); 93 | return manyParms(users[0],noArgFunction()[0],'Hello','How','are','you','today,'); 94 | function noArgFunction() { 95 | var salaries = [10000,200000]; 96 | salaries[1] = 900000; 97 | return salaries; 98 | } 99 | function updateSalary(users,salaries) { 100 | users.map(function(user) { 101 | user['salary'] = salaries[i]; 102 | user['age'] = age; 103 | i++; 104 | }); 105 | } 106 | function manyParms(user,salary,a,b,c,d,e) { 107 | return a+' '+b+' '+c+' '+d+' '+e+' '+user.name+'. You made \$'+salary; 108 | } 109 | 110 | """; 111 | Map context = initContext(); 112 | dynamic rtnValue = JSInterpreter.fromCode(codeToEvaluate,context).evaluate(); 113 | expect(context['users'][0]['name'],'Khurram'); 114 | expect(context['users'][0]['salary'],10000); 115 | expect(context['users'][1]['salary'],900000); 116 | expect(context['users'][1]['age'],3); 117 | expect(rtnValue,'Hello How are you today, Khurram. You made \$10000'); 118 | }); 119 | ``` 120 | 121 | -------------------------------------------------------------------------------- /assets/basic.yaml: -------------------------------------------------------------------------------- 1 | View: 2 | title: Guess your gender from your name 3 | items: 4 | - a. First Name: TextInput 5 | - b. Find My Gender: Button 6 | - c. Gender: TextInput 7 | 8 | APIs: 9 | genderAPI: 10 | uri: https://gender-api.com/get 11 | parameters: 12 | key: gE3CT26ctLhjufjXlKRMnqFrNLgSDcN4tPMK 13 | name: 14 | 15 | 16 | Actions: 17 | b: 18 | click: 19 | call: 20 | api: genderAPI 21 | parameters: 22 | name: a.value 23 | success: |- 24 | { 25 | "type": "Program", 26 | "body": [ 27 | { 28 | "type": "ExpressionStatement", 29 | "expression": { 30 | "type": "AssignmentExpression", 31 | "operator": "=", 32 | "left": { 33 | "type": "MemberExpression", 34 | "object": { 35 | "type": "Identifier", 36 | "name": "c" 37 | }, 38 | "property": { 39 | "type": "Identifier", 40 | "name": "value" 41 | }, 42 | "computed": false 43 | }, 44 | "right": { 45 | "type": "MemberExpression", 46 | "object": { 47 | "type": "Identifier", 48 | "name": "response" 49 | }, 50 | "property": { 51 | "type": "Identifier", 52 | "name": "gender" 53 | }, 54 | "computed": false 55 | } 56 | } 57 | } 58 | ], 59 | "sourceType": "script" 60 | } 61 | 62 | 63 | Layout: 64 | Form: 65 | spaceBetween: 2 66 | items: 67 | - a 68 | - c 69 | - b -------------------------------------------------------------------------------- /assets/basic_conditionals.yaml: -------------------------------------------------------------------------------- 1 | View: 2 | title: Guess your gender from your name 3 | items: 4 | - a. First Name: TextInput 5 | - b. Find My Gender: Button 6 | - c. Gender: TextInput 7 | 8 | APIs: 9 | genderAPI: 10 | uri: https://gender-api.com/get 11 | parameters: 12 | key: gE3CT26ctLhjufjXlKRMnqFrNLgSDcN4tPMK 13 | name: 14 | 15 | 16 | Actions: 17 | b: 18 | click: 19 | call: 20 | api: genderAPI 21 | parameters: 22 | name: a.value 23 | success: |- 24 | { 25 | "type": "Program", 26 | "start": 0, 27 | "end": 235, 28 | "body": [ 29 | { 30 | "type": "IfStatement", 31 | "start": 54, 32 | "end": 203, 33 | "test": { 34 | "type": "BinaryExpression", 35 | "start": 59, 36 | "end": 84, 37 | "left": { 38 | "type": "MemberExpression", 39 | "start": 59, 40 | "end": 74, 41 | "object": { 42 | "type": "Identifier", 43 | "start": 59, 44 | "end": 67, 45 | "name": "response" 46 | }, 47 | "property": { 48 | "type": "Identifier", 49 | "start": 68, 50 | "end": 74, 51 | "name": "gender" 52 | }, 53 | "computed": false 54 | }, 55 | "operator": "==", 56 | "right": { 57 | "type": "Literal", 58 | "start": 78, 59 | "end": 84, 60 | "value": "male", 61 | "raw": "'male'" 62 | } 63 | }, 64 | "consequent": { 65 | "type": "BlockStatement", 66 | "start": 87, 67 | "end": 109, 68 | "body": [ 69 | { 70 | "type": "ExpressionStatement", 71 | "start": 90, 72 | "end": 107, 73 | "expression": { 74 | "type": "AssignmentExpression", 75 | "start": 90, 76 | "end": 106, 77 | "operator": "=", 78 | "left": { 79 | "type": "MemberExpression", 80 | "start": 90, 81 | "end": 97, 82 | "object": { 83 | "type": "Identifier", 84 | "start": 90, 85 | "end": 91, 86 | "name": "c" 87 | }, 88 | "property": { 89 | "type": "Identifier", 90 | "start": 92, 91 | "end": 97, 92 | "name": "value" 93 | }, 94 | "computed": false 95 | }, 96 | "right": { 97 | "type": "Literal", 98 | "start": 100, 99 | "end": 106, 100 | "value": "Male", 101 | "raw": "'Male'" 102 | } 103 | } 104 | } 105 | ] 106 | }, 107 | "alternate": { 108 | "type": "IfStatement", 109 | "start": 115, 110 | "end": 203, 111 | "test": { 112 | "type": "BinaryExpression", 113 | "start": 120, 114 | "end": 147, 115 | "left": { 116 | "type": "MemberExpression", 117 | "start": 120, 118 | "end": 135, 119 | "object": { 120 | "type": "Identifier", 121 | "start": 120, 122 | "end": 128, 123 | "name": "response" 124 | }, 125 | "property": { 126 | "type": "Identifier", 127 | "start": 129, 128 | "end": 135, 129 | "name": "gender" 130 | }, 131 | "computed": false 132 | }, 133 | "operator": "==", 134 | "right": { 135 | "type": "Literal", 136 | "start": 139, 137 | "end": 147, 138 | "value": "female", 139 | "raw": "'female'" 140 | } 141 | }, 142 | "consequent": { 143 | "type": "BlockStatement", 144 | "start": 149, 145 | "end": 173, 146 | "body": [ 147 | { 148 | "type": "ExpressionStatement", 149 | "start": 152, 150 | "end": 171, 151 | "expression": { 152 | "type": "AssignmentExpression", 153 | "start": 152, 154 | "end": 170, 155 | "operator": "=", 156 | "left": { 157 | "type": "MemberExpression", 158 | "start": 152, 159 | "end": 159, 160 | "object": { 161 | "type": "Identifier", 162 | "start": 152, 163 | "end": 153, 164 | "name": "c" 165 | }, 166 | "property": { 167 | "type": "Identifier", 168 | "start": 154, 169 | "end": 159, 170 | "name": "value" 171 | }, 172 | "computed": false 173 | }, 174 | "right": { 175 | "type": "Literal", 176 | "start": 162, 177 | "end": 170, 178 | "value": "Female", 179 | "raw": "'Female'" 180 | } 181 | } 182 | } 183 | ] 184 | }, 185 | "alternate": { 186 | "type": "BlockStatement", 187 | "start": 179, 188 | "end": 203, 189 | "body": [ 190 | { 191 | "type": "ExpressionStatement", 192 | "start": 182, 193 | "end": 201, 194 | "expression": { 195 | "type": "AssignmentExpression", 196 | "start": 182, 197 | "end": 200, 198 | "operator": "=", 199 | "left": { 200 | "type": "MemberExpression", 201 | "start": 182, 202 | "end": 189, 203 | "object": { 204 | "type": "Identifier", 205 | "start": 182, 206 | "end": 183, 207 | "name": "c" 208 | }, 209 | "property": { 210 | "type": "Identifier", 211 | "start": 184, 212 | "end": 189, 213 | "name": "value" 214 | }, 215 | "computed": false 216 | }, 217 | "right": { 218 | "type": "Literal", 219 | "start": 191, 220 | "end": 200, 221 | "value": "unknown", 222 | "raw": "'unknown'" 223 | } 224 | } 225 | } 226 | ] 227 | } 228 | } 229 | } 230 | ], 231 | "sourceType": "script" 232 | } 233 | Layout: 234 | Form: 235 | spaceBetween: 2 236 | items: 237 | - a 238 | - c 239 | - b -------------------------------------------------------------------------------- /assets/basic_raw.yaml: -------------------------------------------------------------------------------- 1 | ID: Screen1 2 | View: 3 | title: Guess your gender from your name 4 | items: 5 | - a. First Name: TextInput 6 | - b. Find My Gender: Button 7 | - c. Gender: TextInput 8 | 9 | APIs: 10 | genderAPI: 11 | uri: https://gender-api.com/get 12 | parameters: 13 | key: gE3CT26ctLhjufjXlKRMnqFrNLgSDcN4tPMK 14 | name: 15 | 16 | Transitions: 17 | - tr1. Screen1 -> Screen2 : slide 18 | - tr2. Screen1 -> Screen3 : 19 | ios: modal_from_bottom 20 | android: default_child 21 | 22 | Actions: 23 | b: 24 | click: 25 | call: 26 | api: genderAPI 27 | parameters: 28 | name: a.value 29 | success: |- 30 | c.value = response.gender; 31 | flowContext.put('gender',c.value); 32 | log('gender retrieved as '+c.value); 33 | tr1.go(); 34 | 35 | Layout: 36 | Form: 37 | spaceBetween: 2 38 | items: 39 | - a 40 | - c 41 | - b -------------------------------------------------------------------------------- /assets/basic_ts.yaml: -------------------------------------------------------------------------------- 1 | ID: Screen1 2 | View: 3 | title: Guess your gender from your name 4 | items: 5 | - a. First Name: TextInput 6 | - b. Find My Gender: Button 7 | - c. Gender: TextInput 8 | 9 | APIs: 10 | genderAPI: 11 | uri: https://gender-api.com/get 12 | parameters: 13 | key: gE3CT26ctLhjufjXlKRMnqFrNLgSDcN4tPMK 14 | name: 15 | 16 | Transitions: 17 | - tr1. Screen1 -> Screen2 : slide 18 | - tr2. Screen1 -> Screen3 : 19 | ios: modal_from_bottom 20 | android: default_child 21 | 22 | Actions: 23 | b: 24 | click: 25 | call: 26 | api: genderAPI 27 | parameters: 28 | name: a.value 29 | #c.value = response.gender 30 | success: |- 31 | { 32 | "type": "Program", 33 | "body": [ 34 | { 35 | "type": "IfStatement", 36 | "test": { 37 | "type": "BinaryExpression", 38 | "operator": "==", 39 | "left": { 40 | "type": "MemberExpression", 41 | "object": { 42 | "type": "Identifier", 43 | "name": "response" 44 | }, 45 | "property": { 46 | "type": "Identifier", 47 | "name": "gender" 48 | }, 49 | "computed": false, 50 | "optional": false 51 | }, 52 | "right": { 53 | "type": "Literal", 54 | "value": "male", 55 | "raw": "'male'" 56 | } 57 | }, 58 | "consequent": { 59 | "type": "BlockStatement", 60 | "body": [ 61 | { 62 | "type": "ExpressionStatement", 63 | "expression": { 64 | "type": "AssignmentExpression", 65 | "operator": "=", 66 | "left": { 67 | "type": "MemberExpression", 68 | "object": { 69 | "type": "Identifier", 70 | "name": "c" 71 | }, 72 | "property": { 73 | "type": "Identifier", 74 | "name": "value" 75 | }, 76 | "computed": false, 77 | "optional": false 78 | }, 79 | "right": { 80 | "type": "Literal", 81 | "value": "Male", 82 | "raw": "'Male'" 83 | } 84 | } 85 | } 86 | ] 87 | }, 88 | "alternate": { 89 | "type": "IfStatement", 90 | "test": { 91 | "type": "BinaryExpression", 92 | "operator": "==", 93 | "left": { 94 | "type": "MemberExpression", 95 | "object": { 96 | "type": "Identifier", 97 | "name": "response" 98 | }, 99 | "property": { 100 | "type": "Identifier", 101 | "name": "gender" 102 | }, 103 | "computed": false, 104 | "optional": false 105 | }, 106 | "right": { 107 | "type": "Literal", 108 | "value": "female", 109 | "raw": "'female'" 110 | } 111 | }, 112 | "consequent": { 113 | "type": "BlockStatement", 114 | "body": [ 115 | { 116 | "type": "ExpressionStatement", 117 | "expression": { 118 | "type": "AssignmentExpression", 119 | "operator": "=", 120 | "left": { 121 | "type": "MemberExpression", 122 | "object": { 123 | "type": "Identifier", 124 | "name": "c" 125 | }, 126 | "property": { 127 | "type": "Identifier", 128 | "name": "value" 129 | }, 130 | "computed": false, 131 | "optional": false 132 | }, 133 | "right": { 134 | "type": "Literal", 135 | "value": "Female", 136 | "raw": "'Female'" 137 | } 138 | } 139 | } 140 | ] 141 | }, 142 | "alternate": { 143 | "type": "BlockStatement", 144 | "body": [ 145 | { 146 | "type": "ExpressionStatement", 147 | "expression": { 148 | "type": "AssignmentExpression", 149 | "operator": "=", 150 | "left": { 151 | "type": "MemberExpression", 152 | "object": { 153 | "type": "Identifier", 154 | "name": "c" 155 | }, 156 | "property": { 157 | "type": "Identifier", 158 | "name": "value" 159 | }, 160 | "computed": false, 161 | "optional": false 162 | }, 163 | "right": { 164 | "type": "Literal", 165 | "value": "Unknown", 166 | "raw": "'Unknown'" 167 | } 168 | } 169 | } 170 | ] 171 | } 172 | } 173 | } 174 | ], 175 | "sourceType": "script" 176 | } 177 | 178 | 179 | Layout: 180 | Form: 181 | spaceBetween: 2 182 | items: 183 | - a 184 | - c 185 | - b -------------------------------------------------------------------------------- /assets/flows.yaml: -------------------------------------------------------------------------------- 1 | Booking: 2 | -------------------------------------------------------------------------------- /assets/hotel_booking.yaml: -------------------------------------------------------------------------------- 1 | View: 2 | title: Hotel Search 3 | items: 4 | - a. *Where are you going : Auto-complete 5 | - b. Dates : Duration 6 | properties: 7 | required: true 8 | start: today 9 | end: today.add(7) 10 | - c. Search : Button 11 | - d. Use Points: Checkbox 12 | - e. : List 13 | item: 14 | - pic: icon 15 | - fav: checkbox 16 | - title 17 | - price 18 | - place 19 | - rating: rating_widget 20 | - reviews 21 | 22 | 23 | Layout: 24 | Form: 25 | spaceBetween: 2 26 | items: 27 | - a 28 | - b 29 | - c 30 | - d -------------------------------------------------------------------------------- /assets/snippet1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Program", 3 | "start": 0, 4 | "end": 218, 5 | "body": [ 6 | { 7 | "type": "ExpressionStatement", 8 | "start": 190, 9 | "end": 216, 10 | "expression": { 11 | "type": "AssignmentExpression", 12 | "start": 190, 13 | "end": 215, 14 | "operator": "=", 15 | "left": { 16 | "type": "MemberExpression", 17 | "start": 190, 18 | "end": 197, 19 | "object": { 20 | "type": "Identifier", 21 | "start": 190, 22 | "end": 191, 23 | "name": "c" 24 | }, 25 | "property": { 26 | "type": "Identifier", 27 | "start": 192, 28 | "end": 197, 29 | "name": "value" 30 | }, 31 | "computed": false 32 | }, 33 | "right": { 34 | "type": "MemberExpression", 35 | "start": 200, 36 | "end": 215, 37 | "object": { 38 | "type": "Identifier", 39 | "start": 200, 40 | "end": 208, 41 | "name": "response" 42 | }, 43 | "property": { 44 | "type": "Identifier", 45 | "start": 209, 46 | "end": 215, 47 | "name": "gender" 48 | }, 49 | "computed": false 50 | } 51 | } 52 | } 53 | ], 54 | "sourceType": "script" 55 | } 56 | -------------------------------------------------------------------------------- /assets/stocks.yaml: -------------------------------------------------------------------------------- 1 | #api key:QE1JSA96LZ8ZC57B 2 | View: 3 | title: Get Stock Price 4 | items: 5 | - a. Stock Symbol: TextInput 6 | - b. Get Real-Time Price: Button 7 | - c. Quote: Text 8 | 9 | APIs: 10 | stockAPI: 11 | uri: https://www.alphavantage.co/query 12 | parameters: 13 | apikey: QE1JSA96LZ8ZC57B 14 | function: GLOBAL_QUOTE 15 | symbol: 16 | 17 | Actions: 18 | b: 19 | click: 20 | call: 21 | api: stockAPI 22 | parameters: 23 | symbol: a.value 24 | success: |- 25 | { 26 | "type": "Program", 27 | "start": 0, 28 | "end": 35, 29 | "body": [ 30 | { 31 | "type": "ExpressionStatement", 32 | "start": 0, 33 | "end": 35, 34 | "expression": { 35 | "type": "AssignmentExpression", 36 | "start": 0, 37 | "end": 34, 38 | "operator": "=", 39 | "left": { 40 | "type": "MemberExpression", 41 | "start": 0, 42 | "end": 7, 43 | "object": { 44 | "type": "Identifier", 45 | "start": 0, 46 | "end": 1, 47 | "name": "c" 48 | }, 49 | "property": { 50 | "type": "Identifier", 51 | "start": 2, 52 | "end": 7, 53 | "name": "value" 54 | }, 55 | "computed": false, 56 | "optional": false 57 | }, 58 | "right": { 59 | "type": "MemberExpression", 60 | "start": 10, 61 | "end": 34, 62 | "object": { 63 | "type": "Identifier", 64 | "start": 10, 65 | "end": 18, 66 | "name": "response" 67 | }, 68 | "property": { 69 | "type": "Literal", 70 | "start": 19, 71 | "end": 33, 72 | "value": "Global Quote", 73 | "raw": "\"Global Quote\"" 74 | }, 75 | "computed": true, 76 | "optional": false 77 | } 78 | } 79 | } 80 | ], 81 | "sourceType": "module" 82 | } 83 | Layout: 84 | Form: 85 | spaceBetween: 2 86 | items: 87 | - a 88 | - b 89 | - c 90 | -------------------------------------------------------------------------------- /lib/action.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:ensemble_ts_interpreter/api.dart'; 4 | import 'package:ensemble_ts_interpreter/view.dart'; 5 | import 'package:flutter/material.dart' hide View; 6 | import 'package:yaml/yaml.dart'; 7 | 8 | enum Event { 9 | click,longPress 10 | } 11 | class EnsembleAction { 12 | } 13 | /* 14 | click: 15 | call: 16 | api: genderAPI 17 | parameters: 18 | name: a 19 | success: c.value={gender} 20 | */ 21 | class WidgetAction implements EnsembleAction { 22 | WidgetView target; 23 | Event event; 24 | View view; 25 | Handler handler; 26 | TextFormField? f; 27 | WidgetAction(this.target,this.event,this.view,this.handler); 28 | static WidgetAction from(WidgetView target,String eventName,View view,Map apis,YamlMap map) { 29 | Event? event; 30 | try { 31 | Event.values.forEach((e) { 32 | if ( e.toString() == 'Event.'+eventName.toLowerCase() ) { 33 | event = e; 34 | } 35 | }); 36 | } catch (e) { 37 | throw Exception("Event by name="+eventName+" is not supported"); 38 | } 39 | WidgetAction? action; 40 | map.forEach((k,v) { 41 | if ( k == "call" ) { 42 | APIHandler handler = APIHandler.from(view, apis,v); 43 | action = WidgetAction(target,event!,view,handler); 44 | if ( event == Event.click ) { 45 | final Widget orig = target.widget; 46 | target.widget = GestureDetector( 47 | onTap: () { 48 | print('ontap on '+orig.key.toString()); 49 | handler.handle(action!); 50 | }, 51 | child: AbsorbPointer(child:orig) 52 | ); 53 | } 54 | } else { 55 | throw Exception('no handler found for event '+event.toString()); 56 | } 57 | }); 58 | return action!; 59 | } 60 | } 61 | class WidgetActions { 62 | static List from(View view,WidgetView target,Map apis,YamlMap map) { 63 | List widgetActions = []; 64 | map.forEach((k,v) { 65 | widgetActions.add(WidgetAction.from(target,k,view,apis,v)); 66 | }); 67 | return widgetActions; 68 | } 69 | } 70 | /* 71 | Actions: 72 | b: 73 | click: 74 | call: 75 | api: genderAPI 76 | parameters: 77 | name: a 78 | success: c.value={gender} 79 | */ 80 | class EnsembleActions { 81 | static List configure(View view,Map apis,YamlMap map) { 82 | List actions = []; 83 | map.forEach((k,v) { 84 | WidgetView? wv = view.get(k); 85 | if ( wv != null ) { 86 | actions.addAll(WidgetActions.from(view,wv,apis,v)); 87 | } 88 | }); 89 | return actions; 90 | } 91 | } 92 | abstract class Handler { 93 | void handle(WidgetAction action); 94 | } 95 | /* 96 | api: genderAPI 97 | parameters: 98 | name: a.value 99 | success: c.value=response.gender 100 | */ 101 | class APIHandler extends Handler { 102 | API api; 103 | Map? paramMetaValues; 104 | String? success; 105 | String? error; 106 | APIHandler(this.api,this.paramMetaValues,this.success,this.error); 107 | static APIHandler from(View view,Map apis,YamlMap map) { 108 | if ( !apis.containsKey(map['api']) ) { 109 | throw Exception('api with name='+map['api']+' not define'); 110 | } 111 | API api = apis[map['api']]!; 112 | Map? paramMetaValues = HashMap(); 113 | if ( map.containsKey('parameters') ) { 114 | map['parameters'].forEach((k,v){ 115 | paramMetaValues[k.toString()] = v.toString(); 116 | }); 117 | } 118 | return APIHandler(api,paramMetaValues,map['success'],map['error']); 119 | } 120 | Map prepareContext(WidgetAction action) { 121 | Map context = HashMap(); 122 | action.view.idWidgetMap.forEach((k, v) { 123 | context[k] = v.widget; 124 | }); 125 | return context; 126 | } 127 | @override 128 | void handle(WidgetAction action) { 129 | Map values = HashMap(); 130 | prepareContext(action); 131 | api.call(values); 132 | // response.then((res) { 133 | // if ( success != null ) { 134 | // var decodedResponse = jsonDecode(utf8.decode(res.bodyBytes)) as Map; 135 | // InvokableMap m = InvokableMap(decodedResponse); 136 | // context["response"] = m; 137 | // var json = jsonDecode(success!); 138 | // List arr = ASTBuilder().buildArray(json['body']); 139 | // Interpreter(context).evaluate(arr); 140 | // } 141 | // 142 | // }); 143 | } 144 | 145 | } 146 | class MyEvaluator { 147 | const MyEvaluator(); 148 | 149 | dynamic evalMemberExpression() { 150 | 151 | } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /lib/api.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'dart:collection'; 4 | import 'dart:convert' as convert; 5 | import 'package:http/http.dart' as http; 6 | import 'package:yaml/yaml.dart'; 7 | 8 | class API { 9 | String name,uri; 10 | Map? params; 11 | String method = 'get'; 12 | API(this.name,this.uri,this.params); 13 | Future call(Map? paramValues) async { 14 | Map m = HashMap(); 15 | if ( params != null ) { 16 | m.addAll(params!); 17 | } 18 | if ( paramValues != null ) { 19 | m.addAll(paramValues); 20 | } 21 | http.Response res; 22 | if ( method == 'get' ) { 23 | Uri _uri = Uri.parse(uri); 24 | 25 | //res = await http.get(Uri.dataFromString(uri, parameters: m)); 26 | res = await http.get(_uri.replace(queryParameters:m)); 27 | } else if ( method == 'post' ) { 28 | res = await http.post(Uri.dataFromString(uri, parameters: m)); 29 | } else { 30 | throw Exception(method+' Method for http is not supported'); 31 | } 32 | return res; 33 | } 34 | static API from(String name,YamlMap map) { 35 | String uri = map['uri']; 36 | Map? params = HashMap(); 37 | if ( map.containsKey('parameters') ) { 38 | map['parameters'].forEach((k,v){ 39 | params[k.toString()] = v.toString(); 40 | }); 41 | } 42 | return API(name,uri,params); 43 | } 44 | } 45 | class APIs { 46 | static Map from(YamlMap map) { 47 | Map apis = HashMap(); 48 | map.forEach((k,v) { 49 | apis[k] = API.from(k,v); 50 | }); 51 | return apis; 52 | } 53 | } -------------------------------------------------------------------------------- /lib/errors.dart: -------------------------------------------------------------------------------- 1 | class JSException implements Exception { 2 | int line; 3 | int? column; 4 | String message; 5 | String? recovery; 6 | String? detailedError; 7 | 8 | // store the original error 9 | dynamic originalError; 10 | 11 | JSException(this.line, this.message, 12 | {this.column = 0, this.detailedError, this.recovery, this.originalError}); 13 | 14 | @override 15 | String toString() { 16 | return 'Exception Occurred while running javascript code: Line: $line Message: $message.' 17 | ' Detailed Error: ${detailedError ?? ''}'; 18 | } 19 | } 20 | 21 | class InvalidPropertyException implements Exception { 22 | String message; 23 | 24 | InvalidPropertyException(this.message); 25 | 26 | @override 27 | String toString() { 28 | return message; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/expression.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Evaluator { 4 | Map context; 5 | Evaluator(this.context); 6 | dynamic eval(String exp) { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /lib/extensions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:http/http.dart'; 7 | 8 | extension Value on TextFormField { 9 | String get value => controller!.value.text; 10 | 11 | set value(String newValue) { 12 | controller!.text = newValue; 13 | } 14 | void setValue(String v) { 15 | controller!.text = v; 16 | } 17 | Map toJson() => {'value': value}; 18 | } 19 | extension Json on Response { 20 | Map toJson() { 21 | return jsonDecode(body); 22 | } 23 | } 24 | 25 | extension WidgetProps on Widget { 26 | 27 | void setProperty(String name,var value) { 28 | if ( name == 'value' ) { 29 | this.value = value; 30 | } 31 | } 32 | getProperty(String name) { 33 | if ( name == 'value' ) { 34 | return value; 35 | } 36 | throw Exception(name+' is not recognized as a property of object '+this.toString()); 37 | } 38 | set value(String newValue) { 39 | if ( this is TextFormField ) { 40 | (this as TextFormField).value = newValue; 41 | } else { 42 | //fix this as we add widgets 43 | throw Exception("value is not supported on "+this.toString()); 44 | } 45 | } 46 | String get value { 47 | String rtn = ''; 48 | if ( this is TextFormField ) { 49 | rtn = (this as TextFormField).value; 50 | } else { 51 | //fix this as we add widgets 52 | throw Exception("value is not supported on "+this.toString()); 53 | } 54 | return rtn; 55 | } 56 | Map toJson() { 57 | Map rtn = HashMap(); 58 | if ( this is TextFormField ) { 59 | rtn = (this as TextFormField).toJson(); 60 | } else { 61 | //fix this as we add widgets 62 | throw Exception("toJson is not supported on "+this.toString()); 63 | } 64 | return rtn; 65 | } 66 | } -------------------------------------------------------------------------------- /lib/invokables/context.dart: -------------------------------------------------------------------------------- 1 | abstract class Context { 2 | void addDataContext(Map data); 3 | void addDataContextById(String id, dynamic value); 4 | bool hasContext(String id); 5 | dynamic getContextById(String id); 6 | Map getContextMap(); 7 | void addToThisContext(String id, dynamic value); 8 | } 9 | 10 | class SimpleContext implements Context { 11 | final Map _dataContext; 12 | 13 | SimpleContext(Map initialData) : _dataContext = initialData; 14 | 15 | @override 16 | void addDataContext(Map data) { 17 | _dataContext.addAll(data); 18 | } 19 | 20 | @override 21 | void addDataContextById(String id, dynamic value) { 22 | _dataContext[id] = value; 23 | } 24 | 25 | @override 26 | bool hasContext(String id) { 27 | return _dataContext.containsKey(id); 28 | } 29 | 30 | @override 31 | dynamic getContextById(String id) { 32 | return _dataContext[id]; 33 | } 34 | 35 | @override 36 | Map getContextMap() { 37 | return _dataContext; 38 | } 39 | 40 | @override 41 | void addToThisContext(String id, value) { 42 | _dataContext[id] = value; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/invokables/invokable.dart: -------------------------------------------------------------------------------- 1 | import 'package:ensemble_ts_interpreter/errors.dart'; 2 | import 'package:ensemble_ts_interpreter/invokables/invokableprimitives.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | 6 | mixin Invokable { 7 | // optional ID to identify this Invokable 8 | String? id; 9 | SourceSpan? definition; 10 | /// mark these functions as protected as we need the implementations, 11 | /// but discourage direct usages. 12 | /// Reasons: 13 | /// 1. There are more base getters/setters/methods from the base class. 14 | /// Users can just focus on defining only applicable fields for their 15 | /// classes but still get the base implementations automatically. 16 | /// 2. setProperty() will automatically notify the controller's listeners 17 | /// for changes, enabling the listeners (widget state) to redraw. 18 | /// 19 | /// Use getProperty/setProperty/callMethod instead of these. 20 | Map getters(); 21 | Map setters(); 22 | Map methods(); 23 | 24 | List? getList(Object obj) { 25 | List? l; 26 | if ( obj is List ) { 27 | l = obj; 28 | } 29 | return l; 30 | } 31 | String? getString(Object obj) { 32 | String? str; 33 | if ( obj is String ) { 34 | str = obj; 35 | } else if ( obj is Map && obj.containsKey('value') ) { 36 | str = obj['value'] as String; 37 | } 38 | return str; 39 | } 40 | Map? getMap(Object obj) { 41 | Map? m; 42 | if ( obj is Map ) { 43 | m = obj; 44 | } 45 | return m; 46 | } 47 | static List getGettableProperties(Invokable obj) { 48 | try { 49 | List rtn = obj.getters().keys.toList(); 50 | if (obj is HasController) { 51 | rtn.addAll((obj as HasController).controller.getBaseGetters().keys); 52 | } 53 | return rtn; 54 | } catch (e) { 55 | //we ignore this, there is no point at this stage to throw an error 56 | } 57 | return []; 58 | } 59 | 60 | static List getSettableProperties(Invokable obj) { 61 | try { 62 | List rtn = obj.setters().keys.toList(); 63 | if (obj is HasController) { 64 | rtn.addAll((obj as HasController).controller.getBaseSetters().keys); 65 | } 66 | return rtn; 67 | } catch (e) { 68 | //we ignore this, there is no point at this stage to throw an error 69 | } 70 | return []; 71 | } 72 | 73 | static Map getMethods(Invokable obj) { 74 | try { 75 | Map rtn = obj.methods(); 76 | if (obj is HasController) { 77 | rtn.addAll((obj as HasController).controller.getBaseMethods()); 78 | } 79 | return rtn; 80 | } catch (e) { 81 | //we ignore this, there is no point at this stage to throw an error 82 | } 83 | return {}; 84 | } 85 | bool hasSettableProperty(dynamic prop) { 86 | return getSettableProperties(this).contains(prop); 87 | } 88 | bool hasGettableProperty(dynamic prop) { 89 | return getGettableProperties(this).contains(prop); 90 | } 91 | Function? getMethod(dynamic method) { 92 | Map rtn = getMethods(this); 93 | if (rtn.containsKey(method)) { 94 | return rtn[method]; 95 | } 96 | return null; 97 | } 98 | bool hasMethod(dynamic method) { 99 | return getMethods(this).containsKey(method); 100 | } 101 | dynamic getProperty(dynamic prop) { 102 | Function? func = getters()[prop]; 103 | if (func == null && this is HasController) { 104 | func = (this as HasController).controller.getBaseGetters()[prop]; 105 | } 106 | 107 | if (func != null) { 108 | return func(); 109 | } 110 | throw InvalidPropertyException('Object with id:${id??''} does not have a gettable property named $prop'); 111 | } 112 | 113 | /// update a property. If this is a HasController (i.e. Widget), notify it of changes 114 | void setProperty(dynamic prop, dynamic val) { 115 | Function? func = setters()[prop]; 116 | if (func == null && this is HasController) { 117 | func = (this as HasController).controller.getBaseSetters()[prop]; 118 | } 119 | 120 | if (func != null) { 121 | func(val); 122 | 123 | // ask our controller to notify its listeners of changes 124 | if (this is HasController) { 125 | (this as HasController) 126 | .controller 127 | .dispatchChanges(KeyValue(prop.toString(), val)); 128 | } else if (this is EnsembleController) { 129 | (this as EnsembleController).notifyListeners(); 130 | } 131 | } else { 132 | throw InvalidPropertyException( 133 | 'Object with id:${id ?? ''} does not have a settable property named $prop'); 134 | } 135 | } 136 | } 137 | mixin MethodExecutor { 138 | dynamic callMethod(String methodName, List args); 139 | } 140 | 141 | /// Base Mixin for Widgets that want to participate in Ensemble widget tree. 142 | /// This works in conjunction with Controller and WidgetState 143 | mixin HasController on StatefulWidget{ 144 | C get controller; 145 | 146 | /// a widget can tell the framework not to automatically evaluate its value 147 | /// while calling the setters can include the passthrough list here. 148 | /// This is useful if the widget wants to evaluate the value later (e.g. 149 | /// evaluate an Action's variables upon the action execution), or it wants 150 | /// to handle the binding listeners itself (e.g. item-template like) 151 | List passthroughSetters() => []; 152 | } 153 | 154 | abstract class EnsembleController extends ChangeNotifier with Invokable { 155 | 156 | } 157 | 158 | abstract class Controller extends ChangeNotifier { 159 | KeyValue? lastSetterProperty; 160 | 161 | // notify listeners of changes 162 | void dispatchChanges(KeyValue changedProperty) { 163 | lastSetterProperty = changedProperty; 164 | notifyListeners(); 165 | } 166 | 167 | // your controllers may want to extend these to provide base implementations 168 | Map getBaseGetters() { 169 | return {}; 170 | } 171 | Map getBaseSetters() { 172 | return {}; 173 | } 174 | Map getBaseMethods() { 175 | return {}; 176 | } 177 | } 178 | 179 | /// purely for type checking so WidgetState implementation 180 | /// has the correct type 181 | mixin WidgetStateMixin { 182 | } 183 | 184 | /// base state for Flutter widgets that want to be of Invokable type 185 | abstract class BaseWidgetState extends State with WidgetStateMixin { 186 | void changeState() { 187 | // trigger widget to rebuild 188 | setState(() { 189 | 190 | }); 191 | } 192 | 193 | @override 194 | void initState() { 195 | super.initState(); 196 | widget.controller.addListener(changeState); 197 | } 198 | @override 199 | void didUpdateWidget(covariant W oldWidget) { 200 | super.didUpdateWidget(oldWidget); 201 | oldWidget.controller.removeListener(changeState); 202 | widget.controller.addListener(changeState); 203 | } 204 | @override 205 | void dispose() { 206 | super.dispose(); 207 | widget.controller.removeListener(changeState); 208 | } 209 | 210 | 211 | } 212 | 213 | class KeyValue { 214 | KeyValue(this.key, this.value); 215 | 216 | String key; 217 | dynamic value; 218 | } 219 | mixin SupportsPrimitiveOperations { 220 | //operator could be any of the primitive operators such as -, + , *, / etc 221 | //rhs is the right hand side 222 | dynamic runOperation(String operator,dynamic rhs); 223 | } -------------------------------------------------------------------------------- /lib/invokables/invokablecommons.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 4 | import 'package:dart_date/dart_date.dart'; 5 | 6 | class JSON extends Object with Invokable { 7 | @override 8 | Map methods() { 9 | return { 10 | 'stringify': (dynamic value) => (value != null )? json.encode(value) : null, 11 | 'parse': (String value) => json.decode(value) 12 | }; 13 | } 14 | 15 | @override 16 | Map getters() { 17 | return {}; 18 | } 19 | 20 | @override 21 | Map setters() { 22 | return {}; 23 | } 24 | } 25 | class StaticDate extends Object with Invokable { 26 | @override 27 | Map getters() { 28 | return {}; 29 | } 30 | 31 | @override 32 | Map methods() { 33 | return { 34 | 'UTC': ([ 35 | dynamic arg1, 36 | dynamic arg2, 37 | dynamic arg3, 38 | dynamic arg4, 39 | dynamic arg5, 40 | dynamic arg6, 41 | dynamic arg7, 42 | ]) => Date.utc(arg1,arg2,arg3,arg4,arg5,arg6,arg7), 43 | 44 | 'init': ([ 45 | dynamic arg1, 46 | dynamic arg2, 47 | dynamic arg3, 48 | dynamic arg4, 49 | dynamic arg5, 50 | dynamic arg6, 51 | dynamic arg7, 52 | ]) => Date.init(arg1,arg2,arg3,arg4,arg5,arg6,arg7), 53 | 'parse': (String strDate) => Date.init([strDate]), 54 | 'now': () => Date.init([]) 55 | }; 56 | } 57 | 58 | @override 59 | Map setters() { 60 | return {}; 61 | } 62 | 63 | } 64 | class Date extends Object with Invokable, SupportsPrimitiveOperations{ 65 | static Date now() { 66 | return Date(DateTime.now()); 67 | } 68 | static Date fromDate(Date d) { 69 | return Date(d.dateTime.clone); 70 | } 71 | static int utc([ 72 | dynamic arg1, 73 | dynamic arg2, 74 | dynamic arg3, 75 | dynamic arg4, 76 | dynamic arg5, 77 | dynamic arg6, 78 | dynamic arg7, 79 | ]) { 80 | if (arg1 != null && arg2 != null) { 81 | int year = arg1; 82 | int month = arg2 + 1; // JavaScript months are zero-based 83 | int day = arg3 != null ? arg3 : 1; 84 | int hour = arg4 != null ? arg4 : 0; 85 | int minute = arg5 != null ? arg5 : 0; 86 | int second = arg6 != null ? arg6 : 0; 87 | int millisecond = arg7 != null ? arg7 : 0; 88 | 89 | return DateTime.utc(year, month, day, hour, minute, second, millisecond).millisecondsSinceEpoch; 90 | } else { 91 | throw ArgumentError('At least 2 parameters are required for utc()'); 92 | } 93 | } 94 | 95 | Date.init([ 96 | dynamic arg1, 97 | dynamic arg2, 98 | dynamic arg3, 99 | dynamic arg4, 100 | dynamic arg5, 101 | dynamic arg6, 102 | dynamic arg7, 103 | ]) { 104 | if (arg1 == null) { 105 | dateTime = DateTime.now(); 106 | } else if (arg2 == null) { 107 | if (arg1 is String) { 108 | dateTime = DateTime.parse(arg1); 109 | } else { 110 | dateTime = DateTime.fromMillisecondsSinceEpoch(arg1); 111 | } 112 | } else { 113 | int year = arg1; 114 | int month = arg2 + 1; // JavaScript months are zero-based 115 | int day = arg3 != null ? arg3 : 1; 116 | int hour = arg4 != null ? arg4 : 0; 117 | int minute = arg5 != null ? arg5 : 0; 118 | int second = arg6 != null ? arg6 : 0; 119 | int millisecond = arg7 != null ? arg7 : 0; 120 | 121 | dateTime = DateTime(year, month, day, hour, minute, second, millisecond); 122 | } 123 | } 124 | 125 | late DateTime dateTime; 126 | 127 | Date(this.dateTime); 128 | 129 | Map getters() { 130 | return { 131 | 'time': () => dateTime.millisecondsSinceEpoch, 132 | 'year': () => dateTime.year, 133 | 'month': () => dateTime.month - 1, // JavaScript months are zero-based 134 | 'day': () => dateTime.day, 135 | 'weekday': () => dateTime.weekday % 7, // JavaScript days are zero-based 136 | 'hour': () => dateTime.hour, 137 | 'minute': () => dateTime.minute, 138 | 'second': () => dateTime.second, 139 | 'millisecond': () => dateTime.millisecond, 140 | 'timezoneOffset': () => -dateTime.timeZoneOffset.inMinutes, 141 | 'isoString': () => dateTime.toUtc().toIso8601String(), 142 | 'localDateString': () => dateTime.toLocal().toString().split(' ')[0], 143 | 'localTimeString': () => 144 | dateTime.toLocal().toString().split(' ')[1].split('.')[0], 145 | 'localString': () => dateTime.toLocal().toString().split('.')[0], 146 | 'utcFullYear': () => dateTime.toUtc().year, 147 | 'utcMonth': () => 148 | dateTime.toUtc().month - 1, // JavaScript months are zero-based 149 | 'utcDate': () => dateTime.toUtc().day, 150 | 'utcDay': () => 151 | dateTime.toUtc().weekday % 7, // JavaScript days are zero-based 152 | 'utcHours': () => dateTime.toUtc().hour, 153 | 'utcMinutes': () => dateTime.toUtc().minute, 154 | 'utcSeconds': () => dateTime.toUtc().second, 155 | 'utcMilliseconds': () => dateTime.toUtc().millisecond, 156 | }; 157 | } 158 | @override 159 | String toJson() { 160 | return dateTime.toUtc().toIso8601String(); 161 | } 162 | Map methods() { 163 | return { 164 | 'getTime': () => dateTime.millisecondsSinceEpoch, 165 | 'getFullYear': () => dateTime.year, 166 | 'getMonth': () => dateTime.month - 1, // JavaScript months are zero-based 167 | 'getDate': () => dateTime.day, 168 | 'getDay': () => dateTime.weekday % 7, // JavaScript days are zero-based 169 | 'getHours': () => dateTime.hour, 170 | 'getMinutes': () => dateTime.minute, 171 | 'getSeconds': () => dateTime.second, 172 | 'getMilliseconds': () => dateTime.millisecond, 173 | 'getTimezoneOffset': () => -dateTime.timeZoneOffset.inMinutes, 174 | 'toISOString': () => dateTime.toUtc().toIso8601String(), 175 | 'toLocaleDateString': () => dateTime.toLocal().toString().split(' ')[0], 176 | 'toLocaleTimeString': () => 177 | dateTime.toLocal().toString().split(' ')[1].split('.')[0], 178 | 'toLocaleString': () => dateTime.toLocal().toString().split('.')[0], 179 | 'toJSON': () => dateTime.toUtc().toIso8601String(), 180 | 'getUTCFullYear': () => dateTime.toUtc().year, 181 | 'getUTCMonth': () => 182 | dateTime.toUtc().month - 1, // JavaScript months are zero-based 183 | 'getUTCDate': () => dateTime.toUtc().day, 184 | 'getUTCDay': () => 185 | dateTime.toUtc().weekday % 7, // JavaScript days are zero-based 186 | 'getUTCHours': () => dateTime.toUtc().hour, 187 | 'getUTCMinutes': () => dateTime.toUtc().minute, 188 | 'getUTCSeconds': () => dateTime.toUtc().second, 189 | 'getUTCMilliseconds': () => dateTime.toUtc().millisecond, 190 | 'setFullYear': (int year) { 191 | dateTime = DateTime(year, dateTime.month, dateTime.day, dateTime.hour, 192 | dateTime.minute, dateTime.second, dateTime.millisecond); 193 | return dateTime.millisecondsSinceEpoch; 194 | }, 195 | 'setMonth': (int month) { 196 | dateTime = DateTime( 197 | dateTime.year, 198 | month + 1, 199 | dateTime.day, 200 | dateTime.hour, 201 | dateTime.minute, 202 | dateTime.second, 203 | dateTime.millisecond); 204 | return dateTime.millisecondsSinceEpoch; 205 | }, 206 | 'setDate': (int day) { 207 | dateTime = DateTime(dateTime.year, dateTime.month, day, dateTime.hour, 208 | dateTime.minute, dateTime.second, dateTime.millisecond); 209 | return dateTime.millisecondsSinceEpoch; 210 | }, 211 | 'setHours': (int hour) { 212 | dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, hour, 213 | dateTime.minute, dateTime.second, dateTime.millisecond); 214 | return dateTime.millisecondsSinceEpoch; 215 | }, 216 | 'setMinutes': (int minute) { 217 | dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, 218 | dateTime.hour, minute, dateTime.second, dateTime.millisecond); 219 | return dateTime.millisecondsSinceEpoch; 220 | }, 221 | 'setSeconds': (int second) { 222 | dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, 223 | dateTime.hour, dateTime.minute, second, dateTime.millisecond); 224 | return dateTime.millisecondsSinceEpoch; 225 | }, 226 | 'setMilliseconds': (int millisecond) { 227 | dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, 228 | dateTime.hour, dateTime.minute, dateTime.second, millisecond); 229 | return dateTime.millisecondsSinceEpoch; 230 | }, 231 | 'setUTCFullYear': (int year) { 232 | dateTime = DateTime.utc( 233 | year, 234 | dateTime.month, 235 | dateTime.day, 236 | dateTime.hour, 237 | dateTime.minute, 238 | dateTime.second, 239 | dateTime.millisecond); 240 | return dateTime.millisecondsSinceEpoch; 241 | }, 242 | 'setUTCMonth': (int month) { 243 | dateTime = DateTime.utc( 244 | dateTime.year, 245 | month + 1, 246 | dateTime.day, 247 | dateTime.hour, 248 | dateTime.minute, 249 | dateTime.second, 250 | dateTime.millisecond); 251 | return dateTime.millisecondsSinceEpoch; 252 | }, 253 | 'setUTCDate': (int day) { 254 | dateTime = DateTime.utc( 255 | dateTime.year, 256 | dateTime.month, 257 | day, 258 | dateTime.hour, 259 | dateTime.minute, 260 | dateTime.second, 261 | dateTime.millisecond); 262 | return dateTime.millisecondsSinceEpoch; 263 | }, 264 | 'setUTCHours': (int hour) { 265 | dateTime = DateTime.utc(dateTime.year, dateTime.month, dateTime.day, 266 | hour, dateTime.minute, dateTime.second, dateTime.millisecond); 267 | return dateTime.millisecondsSinceEpoch; 268 | }, 269 | 'setUTCMinutes': (int minute) { 270 | dateTime = DateTime.utc(dateTime.year, dateTime.month, dateTime.day, 271 | dateTime.hour, minute, dateTime.second, dateTime.millisecond); 272 | return dateTime.millisecondsSinceEpoch; 273 | }, 274 | 'setUTCSeconds': (int second) { 275 | dateTime = DateTime.utc(dateTime.year, dateTime.month, dateTime.day, 276 | dateTime.hour, dateTime.minute, second, dateTime.millisecond); 277 | return dateTime.millisecondsSinceEpoch; 278 | }, 279 | 'setUTCMilliseconds': (int millisecond) { 280 | dateTime = DateTime.utc(dateTime.year, dateTime.month, dateTime.day, 281 | dateTime.hour, dateTime.minute, dateTime.second, millisecond); 282 | return dateTime.millisecondsSinceEpoch; 283 | }, 284 | 'setTime': (int milliseconds) { 285 | dateTime = DateTime.fromMillisecondsSinceEpoch(milliseconds); 286 | return dateTime.millisecondsSinceEpoch; 287 | }, 288 | 'valueOf': () => dateTime.millisecondsSinceEpoch, 289 | 'toString': () => dateTime.toString() 290 | }; 291 | } 292 | Map setters() { 293 | return { 294 | 295 | }; 296 | } 297 | @override 298 | String toString() { 299 | return dateTime.toString(); 300 | } 301 | 302 | @override 303 | runOperation(String operator, dynamic rhs) { 304 | int left = dateTime.millisecondsSinceEpoch; 305 | int right = 0; 306 | if (rhs is Date) { 307 | right = rhs.dateTime.millisecondsSinceEpoch; 308 | } else if (rhs is num) { 309 | right = rhs.toInt(); 310 | } else if (rhs is String) { 311 | right = DateTime.parse(rhs).millisecondsSinceEpoch; 312 | } 313 | dynamic rtn; 314 | switch (operator) { 315 | case '==': 316 | rtn = left == right; 317 | break; 318 | case '!=': 319 | rtn = left != right; 320 | break; 321 | case '<': 322 | rtn = left < right; 323 | break; 324 | case '<=': 325 | rtn = left <= right; 326 | break; 327 | case '>': 328 | rtn = left > right; 329 | break; 330 | case '>=': 331 | rtn = left >= right; 332 | break; 333 | case '-': 334 | rtn = left - right; 335 | break; 336 | case '+': 337 | rtn = left + right; 338 | break; 339 | case '/': 340 | rtn = left / right; 341 | break; 342 | case '*': 343 | rtn = left * right; 344 | break; 345 | case '%': 346 | rtn = left % right; 347 | break; 348 | case '|': 349 | rtn = left | right; 350 | break; 351 | case '^': 352 | rtn = left ^ right; 353 | break; 354 | case '<<': 355 | rtn = left << right; 356 | break; 357 | case '>>': 358 | rtn = left >> right; 359 | break; 360 | case '&': 361 | rtn = left & right; 362 | break; 363 | default: 364 | throw ArgumentError('Unrecognized operator ${operator}'); 365 | } 366 | return rtn; 367 | } 368 | 369 | } -------------------------------------------------------------------------------- /lib/invokables/invokablecontroller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:core'; 3 | 4 | import 'package:ensemble_ts_interpreter/errors.dart'; 5 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 6 | import 'package:ensemble_ts_interpreter/invokables/invokablecommons.dart'; 7 | import 'package:ensemble_ts_interpreter/invokables/invokablemath.dart'; 8 | import 'package:ensemble_ts_interpreter/invokables/invokableprimitives.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:json_path/json_path.dart'; 11 | abstract class GlobalContext { 12 | static RegExp regExp(String regex, String options) { 13 | RegExp r = RegExp(regex); 14 | return r; 15 | } 16 | 17 | static Map _context = { 18 | 'regExp': regExp, 19 | 'Math': InvokableMath(), 20 | 'parseFloat': (dynamic value) { 21 | if (value is String) { 22 | return double.tryParse(value) ?? double.nan; 23 | } else if (value is num) { 24 | return value.toDouble(); 25 | } else { 26 | return double.nan; 27 | } 28 | }, 29 | 'parseInt': (dynamic value, [int? radix = 10]) { 30 | // Directly return the value if it's already an integer 31 | if (value is int) return value; 32 | 33 | // Convert to string to handle both String and double inputs 34 | String stringValue = value.toString(); 35 | 36 | // Handling radix for non-decimal numbers correctly requires parsing integers only 37 | if (radix != null && radix >= 2 && radix <= 36) { 38 | // Check if the value is a valid integer for the specified radix 39 | int? parsedInt = int.tryParse(stringValue, radix: radix); 40 | if (parsedInt != null) return parsedInt; 41 | 42 | // If parsing as int fails, try double and then convert to int 43 | double? parsedDouble = double.tryParse(stringValue); 44 | if (parsedDouble != null) return parsedDouble.toInt(); 45 | } else { 46 | // Fallback to parsing as a decimal number if no valid radix is provided 47 | double? parsedDouble = double.tryParse(stringValue); 48 | if (parsedDouble != null) return parsedDouble.toInt(); 49 | } 50 | 51 | // Return 0 if all parsing attempts fail 52 | return double.nan; 53 | }, 54 | 55 | 'parseDouble': (dynamic value) { 56 | if (value is String) { 57 | return double.tryParse(value) ?? double.nan; 58 | } else if (value is num) { 59 | return value.toDouble(); 60 | } else { 61 | return double.nan; 62 | } 63 | }, 64 | 65 | 'JSON': JSON(), 66 | 'btoa': _String.btoa, 67 | 'atob': _String.atob, 68 | 'console': Console(), 69 | 'Date': StaticDate(), 70 | // Encode and Decode URI Component functions 71 | 'encodeURIComponent': (String s) => Uri.encodeComponent(s), 72 | 'decodeURIComponent': (String s) => Uri.decodeComponent(s), 73 | // Encode and Decode URI functions 74 | 'encodeURI': (String uri) => Uri.encodeFull(uri), 75 | 'decodeURI': (String uri) => Uri.decodeFull(uri), 76 | }; 77 | 78 | static get context => _context; 79 | } 80 | 81 | class InvokableController { 82 | static bool isPrimitive(dynamic val) { 83 | bool rtn = val == null; 84 | if (!rtn) { 85 | rtn = val is String || val is num || val is bool; //add more 86 | } 87 | return rtn; 88 | } 89 | 90 | static bool isNative(dynamic val) { 91 | bool rtn = isPrimitive(val); 92 | if (!rtn) { 93 | rtn = val is Map || val is List || val is RegExp; 94 | } 95 | return rtn; 96 | } 97 | 98 | // // Sample function to simulate a changing condition 99 | // static bool isConditionMet() { 100 | // // Replace this with your actual condition-checking code 101 | // return DateTime.now().second % 10 == 0; 102 | // } 103 | // 104 | // // Function to wait until the condition is met 105 | // static Future waitForCondition() async { 106 | // while (!isConditionMet()) { 107 | // print('Condition not met, waiting 500ms...'); 108 | // await Future.delayed(Duration(milliseconds: 500)); 109 | // } 110 | // print('Condition met! Continuing execution...'); 111 | // } 112 | static void addGlobals(Map context) { 113 | context.addAll(GlobalContext.context); 114 | // context['debug'] = () async { 115 | // await waitForCondition(); 116 | // }; 117 | } 118 | 119 | static Map methods(dynamic val) { 120 | if (val == null) { 121 | return {}; 122 | } else if (val is Invokable) { 123 | return val.methods(); 124 | } else if (val is String) { 125 | return _String.methods(val); 126 | } else if ( val is bool ) { 127 | return _Boolean.methods(val); 128 | } else if ( val is num ) { 129 | return _Number.methods(val); 130 | } else if ( val is Map ) { 131 | return _Map.methods(val); 132 | } else if ( val is List ) { 133 | return _List.methods(val); 134 | } else if ( val is RegExp ) { 135 | return _RegExp.methods(val); 136 | } 137 | return {}; 138 | } 139 | static Map setters(dynamic val) { 140 | if ( val == null ) { 141 | return {}; 142 | } else if ( val is Invokable ) { 143 | return val.setters(); 144 | } else if ( val is String) { 145 | return _String.setters(val); 146 | } else if ( val is bool ) { 147 | return _Boolean.setters(val); 148 | } else if ( val is num ) { 149 | return _Number.setters(val); 150 | } else if ( val is Map ) { 151 | return _Map.setters(val); 152 | } else if ( val is List ) { 153 | return _List.setters(val); 154 | } else if ( val is RegExp ) { 155 | return _RegExp.setters(val); 156 | } 157 | return {}; 158 | } 159 | static Map getters(dynamic val) { 160 | if ( val == null ) { 161 | return {}; 162 | } else if ( val is Invokable ) { 163 | return val.getters(); 164 | } else if ( val is String) { 165 | return _String.getters(val); 166 | } else if ( val is bool ) { 167 | return _Boolean.getters(val); 168 | } else if ( val is num ) { 169 | return _Number.getters(val); 170 | } else if ( val is Map ) { 171 | return _Map.getters(val); 172 | } else if ( val is List ) { 173 | return _List.getters(val); 174 | } else if ( val is RegExp ) { 175 | return _RegExp.getters(val); 176 | } 177 | return {}; 178 | } 179 | static dynamic getProperty(dynamic val, dynamic prop) { 180 | if ( val == null ) { 181 | throw InvalidPropertyException('Cannot get a property on a null object. Property=$prop'); 182 | } else if ( val is Invokable ) { 183 | return val.getProperty(prop); 184 | } else if ( val is String) { 185 | return _String.getProperty(val, prop); 186 | } else if ( val is bool ) { 187 | return _Boolean.getProperty(val, prop); 188 | } else if ( val is num ) { 189 | return _Number.getProperty(val, prop); 190 | } else if ( val is Map ) { 191 | return _Map.getProperty(val, prop); 192 | } else if ( val is List ) { 193 | return _List.getProperty(val, prop); 194 | } else if ( val is RegExp ) { 195 | return _RegExp.getProperty(val, prop); 196 | } 197 | return null; 198 | } 199 | static dynamic setProperty(dynamic val, dynamic prop, dynamic value) { 200 | if ( val == null ) { 201 | throw InvalidPropertyException('Cannot set a property on a null object. Property=$prop and prop value=$value'); 202 | } else if ( val is Invokable ) { 203 | return val.setProperty(prop,value); 204 | } else if ( val is String) { 205 | return _String.setProperty(val, prop, value); 206 | } else if ( val is bool ) { 207 | return _Boolean.setProperty(val, prop, value); 208 | } else if ( val is num ) { 209 | return _Number.setProperty(val, prop, value); 210 | } else if ( val is Map ) { 211 | return _Map.setProperty(val, prop, value); 212 | } else if ( val is List ) { 213 | return _List.setProperty(val, prop, value); 214 | } else if ( val is RegExp ) { 215 | return _RegExp.setProperty(val, prop, value); 216 | } 217 | return {}; 218 | } 219 | static List getGettableProperties(dynamic obj) { 220 | if ( obj is Invokable ) { 221 | return Invokable.getGettableProperties(obj); 222 | } else { 223 | return InvokableController.getters(obj).keys.toList(); 224 | } 225 | } 226 | static List getSettableProperties(dynamic obj) { 227 | if ( obj is Invokable ) { 228 | return Invokable.getSettableProperties(obj); 229 | } else { 230 | return InvokableController.setters(obj).keys.toList(); 231 | } 232 | } 233 | static Map getMethods(dynamic obj) { 234 | if ( obj is Invokable ) { 235 | return Invokable.getMethods(obj); 236 | } else { 237 | return InvokableController.methods(obj); 238 | } 239 | } 240 | 241 | } 242 | class Console extends Object with Invokable { 243 | @override 244 | Map getters() { 245 | return {}; 246 | } 247 | 248 | @override 249 | Map methods() { 250 | return { 251 | 'log': (val) => debugPrint(val?.toString()) 252 | }; 253 | } 254 | 255 | @override 256 | Map setters() { 257 | return {}; 258 | } 259 | 260 | } 261 | class _String { 262 | //encodes string to base64 string 263 | static String btoa(String s) { 264 | final List bytes = utf8.encode(s); 265 | return base64.encode(bytes); 266 | } 267 | //decodes a base64 string 268 | static String atob(String s) => utf8.decode(base64.decode(s)); 269 | static Map getters(String val) { 270 | return { 271 | 'length': () => val.length 272 | }; 273 | } 274 | static dynamic getProperty(String obj, dynamic prop) { 275 | Function? f = getters(obj)[prop]; 276 | if ( f != null ) { 277 | return f(); 278 | } 279 | throw InvalidPropertyException('$obj does not have a gettable property named $prop'); 280 | } 281 | static void setProperty(String obj, dynamic prop, dynamic val) { 282 | Function? func = setters(obj)[prop]; 283 | if (func != null) { 284 | func(val); 285 | } else { 286 | throw InvalidPropertyException('$obj does not have a settable property named $prop'); 287 | } 288 | } 289 | 290 | static String replaceWithJsRegex(String input, RegExp regExp, String replacement, {bool replaceFirst = false}) { 291 | // The replace function takes a Match object and returns the replaced string 292 | String replace(Match match) { 293 | String result = replacement; 294 | 295 | // Replace all group references in the replacement string 296 | for (int i = 0; i <= match.groupCount; i++) { 297 | result = result.replaceAll('\$$i', match.group(i) ?? ''); 298 | } 299 | 300 | return result; 301 | }; 302 | 303 | // Use replaceFirstMapped if replaceFirst is true, otherwise use replaceAllMapped 304 | return replaceFirst ? input.replaceFirstMapped(regExp, replace) : input.replaceAllMapped(regExp, replace); 305 | } 306 | 307 | static Map methods(String val) { 308 | return { 309 | 'indexOf': (String str) => val.indexOf(str), 310 | 'lastIndexOf': (String str,[start=-1]) => (start == -1)?val.lastIndexOf(str):val.lastIndexOf(str,start), 311 | 'charAt': (index)=> val[index], 312 | 'startsWith': (str) => val.startsWith(str), 313 | 'endsWith': (str) => val.endsWith(str), 314 | 'includes': (str) => val.contains(str), 315 | 'toLowerCase': () => val.toLowerCase(), 316 | 'toUpperCase': () => val.toUpperCase(), 317 | 'trim': () => val.trim(), 318 | 'trimStart': () => val.trimLeft(), 319 | 'trimEnd': () => val.trimRight(), 320 | 'localeCompare': (String str) => val.compareTo(str),//not locale specific 321 | 'repeat': (int count) => val * count, 322 | 'search': (RegExp pattern) => pattern.hasMatch(val) ? pattern.firstMatch(val)?.start : -1, 323 | 'slice': (int start, [int? end]) { 324 | int adjustedStart = start < 0 ? val.length + start : start; 325 | adjustedStart = adjustedStart.clamp(0, val.length); 326 | int adjustedEnd = end == null ? val.length : (end < 0 ? val.length + end : end); 327 | adjustedEnd = adjustedEnd.clamp(adjustedStart, val.length); 328 | return val.substring(adjustedStart, adjustedEnd); 329 | }, 330 | 'substr': (int start, [int? length]) => val.substring(start, start + (length ?? val.length - start)), 331 | 'match': (regexp) { 332 | final matches = (regexp as RegExp).allMatches(val); 333 | List list = []; 334 | for ( final m in matches ) { 335 | list.add(m[0]!); 336 | } 337 | return list; 338 | }, 339 | 'matchAll': (regexp) { 340 | final matches = (regexp as RegExp).allMatches(val); 341 | List list = []; 342 | for ( final m in matches ) { 343 | list.add(m[0]!); 344 | } 345 | return list; 346 | }, 347 | 'padStart': (n,[str=' ']) => val.padLeft(n,str), 348 | 'padEnd': (n,[str=' ']) => val.padRight(n,str), 349 | 'substring': (start,[end=-1]) => (end == -1)?val.substring(start):val.substring(start,end), 350 | 'split': (String delimiter) => val.split(delimiter), 351 | 'prettyCurrency': () => InvokablePrimitive.prettyCurrency(val), 352 | 'prettyDate': () => InvokablePrimitive.prettyDate(val), 353 | 'prettyDateTime': () => InvokablePrimitive.prettyDateTime(val), 354 | 'prettyTime': () => InvokablePrimitive.prettyTime(val), 355 | 'replace': (pattern,replacement) { 356 | if ( pattern is String ) { 357 | return val.replaceFirst(pattern, replacement); 358 | } 359 | return replaceWithJsRegex(val, pattern, replacement, replaceFirst: true); 360 | }, 361 | 'replaceAll': (pattern,replacement) { 362 | if ( pattern is String ) { 363 | return val.replaceAll(pattern, replacement); 364 | } 365 | return replaceWithJsRegex(val, pattern, replacement); 366 | }, 367 | 'replaceAllMapped': (pattern,replacement) => replaceWithJsRegex(val, pattern, replacement), 368 | 'tryParseInt':() => int.tryParse(val), 369 | 'tryParseDouble':() => double.tryParse(val), 370 | 'btoa': () => btoa(val), 371 | 'atob': () => atob(val), 372 | }; 373 | } 374 | static Map setters(String val) { 375 | return {}; 376 | } 377 | } 378 | class _Boolean { 379 | static Map getters(bool val) { 380 | return {}; 381 | } 382 | 383 | static Map methods(bool val) { 384 | return {}; 385 | } 386 | 387 | static Map setters(bool val) { 388 | return {}; 389 | } 390 | static dynamic getProperty(bool obj, dynamic prop) { 391 | Function? f = getters(obj)[prop]; 392 | if ( f != null ) { 393 | return f(); 394 | } 395 | throw InvalidPropertyException('$obj does not have a gettable property named $prop'); 396 | } 397 | static void setProperty(bool obj, dynamic prop, dynamic val) { 398 | Function? func = setters(obj)[prop]; 399 | if (func != null) { 400 | func(val); 401 | } else { 402 | throw InvalidPropertyException('$obj does not have a settable property named $prop'); 403 | } 404 | } 405 | } 406 | class _Number { 407 | static Map getters(num val) { 408 | return {}; 409 | } 410 | static Map methods(num val) { 411 | return { 412 | 'prettyCurrency': () => InvokablePrimitive.prettyCurrency(val), 413 | 'prettyDate': () => InvokablePrimitive.prettyDate(val), 414 | 'prettyDateTime': () => InvokablePrimitive.prettyDateTime(val), 415 | 'prettyDuration': () => InvokablePrimitive.prettyDuration(val), 416 | 'toFixed': (int fractionDigits) => val.toStringAsFixed(fractionDigits), 417 | 'toString': ([int? radix]) { 418 | if ( radix != null ) { 419 | return val.toInt().toRadixString(radix); 420 | } 421 | return val.toString(); 422 | } 423 | }; 424 | } 425 | static Map setters(num val) { 426 | return {}; 427 | } 428 | static dynamic getProperty(num obj, dynamic prop) { 429 | Function? f = getters(obj)[prop]; 430 | if ( f != null ) { 431 | return f(); 432 | } 433 | throw InvalidPropertyException('$obj does not have a gettable property named $prop'); 434 | } 435 | static void setProperty(num obj, dynamic prop, dynamic val) { 436 | Function? func = setters(obj)[prop]; 437 | if (func != null) { 438 | func(val); 439 | } else { 440 | throw InvalidPropertyException('$obj does not have a settable property named $prop'); 441 | } 442 | } 443 | } 444 | class _Map { 445 | static Map getters(Map map) { 446 | return { }; 447 | } 448 | static Map methods(Map map) { 449 | return { 450 | 'path':(String path,Function? mapFunction) { 451 | return JsonPath(path) 452 | .read(map) 453 | .map((match)=>(mapFunction!=null)?mapFunction([match.value]):match.value) 454 | .toList(); 455 | }, 456 | 'keys': () => map.keys.toList(), 457 | 'values': () => map.values.toList(), 458 | 'entries': () { 459 | List list = []; 460 | map.forEach((key, value) { 461 | list.add({'key': key, 'value':value}); 462 | }); 463 | return list; 464 | } 465 | 466 | 467 | }; 468 | } 469 | static Map setters(Map val) { 470 | return {}; 471 | } 472 | static dynamic getProperty(Map map, dynamic prop) { 473 | return map[prop]; 474 | } 475 | 476 | static void setProperty(Map map, dynamic prop, dynamic val) { 477 | map[prop] = val; 478 | } 479 | } 480 | class _List { 481 | static Map getters(List list) { 482 | return { 483 | 'length':() => list.length 484 | }; 485 | } 486 | static List filter(List list,Function f) { 487 | return list.where((e) => f([e])).toList(); 488 | } 489 | static Map methods(List list) { 490 | return { 491 | 'map': (Function f) => list 492 | .asMap() 493 | .entries 494 | .map((entry) => f([entry.value, entry.key])) 495 | .toList(), 496 | 'filter': (Function f) => list 497 | .asMap() 498 | .entries 499 | .where((entry) => f([entry.value, entry.key])) 500 | .map((entry) => entry.value) 501 | .toList(), 502 | 'forEach': (Function f) => 503 | list.asMap().forEach((index, element) => f([element, index])), 504 | 'add': (dynamic val) => list.add(val), 505 | 'push': (dynamic val) => list.add(val), 506 | 'indexOf': (dynamic val) => list.indexOf(val), 507 | 'lastIndexOf': (dynamic val) => list.lastIndexOf(val), 508 | 'unique': () => list.toSet().toList(), 509 | 'sort': ([Function? f]) { 510 | if (f == null) { 511 | list.sort(); 512 | } else { 513 | list.sort((a, b) => f([a, b])); 514 | } 515 | return list; 516 | 517 | }, 518 | 'sortF': ([Function? f]) { 519 | if ( f == null ) { 520 | list.sort(); 521 | } else { 522 | list.sort((a,b)=> f([a,b])); 523 | } 524 | return list; 525 | }, 526 | 'at': (int index) => list[index], 527 | 'concat': (List arr) => list + arr, 528 | 'find': (Function f) => list.firstWhere((e) => f([e]), orElse: () => -1), 529 | 'includes': (dynamic v) => list.contains(v), 530 | 'contains': (dynamic v) => list.contains(v), 531 | 'join': ([String str = ',']) => list.join(str), 532 | 'pop': () => (list.isNotEmpty) ? list.removeLast() : null, 533 | 'reduce': (Function f, [dynamic initialValue]) { 534 | // Check if an initial value is provided 535 | if (initialValue != null) { 536 | // Use fold when an initial value is provided 537 | return list.fold(initialValue, 538 | (currentValue, element) => f([currentValue, element])); 539 | } else { 540 | // Use reduce directly when no initial value is provided 541 | // This will throw if the list is empty, similar to JS reduce without an initial value 542 | return list 543 | .reduce((currentValue, element) => f([currentValue, element])); 544 | } 545 | }, 546 | 'reverse': () => list.reversed.toList(), 547 | 'slice': (int start, [int? end]) => 548 | list.sublist(start, end ?? list.length), 549 | 'shift': () => list.isNotEmpty ? list.removeAt(0) : null, 550 | 'unshift': (dynamic val) { 551 | list.insert(0, val); 552 | return list.length; 553 | }, 554 | 'splice': (int start, int deleteCount, [dynamic items]) { 555 | var removedItems = list.sublist(start, start + deleteCount); 556 | list.removeRange(start, start + deleteCount); 557 | if (items != null) { 558 | if (items is List) { 559 | list.insertAll(start, items); 560 | } else { 561 | list.insert(start, items); 562 | } 563 | } 564 | return removedItems; 565 | }, 566 | 'some': (Function f) => list.any((element) => f([element])), 567 | 'every': (Function f) => list.every((element) => f([element])), 568 | 'findIndex': (Function f) => list.indexWhere((e) => f([e])), 569 | 'fill': (dynamic value, [int start = 0, int? end]) { 570 | end ??= list.length; 571 | for (int i = start; i < end; i++) { 572 | if (i >= 0 && i < list.length) { 573 | list[i] = value; 574 | } 575 | } 576 | return list; 577 | }, 578 | }; 579 | } 580 | 581 | static Map setters(List list) { 582 | return {}; 583 | } 584 | 585 | static dynamic getProperty(List list, dynamic prop) { 586 | if ( prop is int ) { 587 | return list[prop]; 588 | } 589 | Function? f = getters(list)[prop]; 590 | if ( f != null ) { 591 | return f(); 592 | } 593 | throw InvalidPropertyException('List or Array does not have a gettable property named $prop'); 594 | } 595 | 596 | static void setProperty(List list, dynamic prop, dynamic val) { 597 | if ( prop is int ) { 598 | if ( prop >= 0 && prop < list.length ) { 599 | list[prop] = val; 600 | } else if ( list.length == prop ) { 601 | list.add(val); 602 | } 603 | } else { 604 | throw InvalidPropertyException('List or Array does not have a settable property named $prop'); 605 | } 606 | } 607 | } 608 | class _RegExp { 609 | static Map getters(RegExp val) { 610 | return {}; 611 | } 612 | 613 | static Map methods(RegExp val) { 614 | return { 615 | 'test': (String input) => val.hasMatch(input), 616 | }; 617 | } 618 | 619 | static Map setters(RegExp val) { 620 | return {}; 621 | } 622 | static dynamic getProperty(RegExp obj, dynamic prop) { 623 | Function? f = getters(obj)[prop]; 624 | if ( f != null ) { 625 | return f(); 626 | } 627 | throw InvalidPropertyException('RegExp does not have a gettable property named $prop'); 628 | } 629 | static void setProperty(RegExp obj, dynamic prop, dynamic val) { 630 | Function? func = setters(obj)[prop]; 631 | if (func != null) { 632 | func(val); 633 | } else { 634 | throw InvalidPropertyException('RegExp does not have a settable property named $prop'); 635 | } 636 | } 637 | } -------------------------------------------------------------------------------- /lib/invokables/invokablemath.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 4 | 5 | class InvokableMath extends Object with Invokable, MethodExecutor { 6 | InvokableMath(); 7 | 8 | @override 9 | Map getters() { 10 | return {}; 11 | } 12 | 13 | dynamic _toNum(dynamic value) { 14 | if (value == null) { 15 | return 0; // Mimic JavaScript coercion of null to 0 16 | } else if (value is num) { 17 | return value; 18 | } else if (value is String) { 19 | return num.tryParse( 20 | value); // Returns null if parsing fails, which will be filtered out 21 | } 22 | return null; // Non-convertible values return null, indicating an inability to coerce 23 | } 24 | 25 | @override 26 | Map methods() { 27 | return { 28 | 'floor': (dynamic n) => _toNum(n)?.floor(), 29 | 'abs': (dynamic n) => _toNum(n)?.abs(), 30 | 'ceil': (dynamic n) => _toNum(n)?.ceil(), 31 | 'round': (dynamic n) => _toNum(n)?.round(), 32 | 'trunc': (dynamic n) => _toNum(n)?.truncate(), 33 | 'log': (dynamic n) => math.log(_toNum(n) ?? 1), 34 | // Default to 1 to prevent log(0) 35 | 'pow': (dynamic x, dynamic y) => math.pow(_toNum(x) ?? 0, _toNum(y) ?? 0), 36 | 'acos': (dynamic n) => math.acos(_toNum(n) ?? 1), 37 | 'asin': (dynamic n) => math.asin(_toNum(n) ?? 0), 38 | 'atan': (dynamic x) => math.atan(_toNum(x) ?? 0), 39 | 'atan2': (dynamic a, dynamic b) => 40 | math.atan2(_toNum(a) ?? 0, _toNum(b) ?? 1), 41 | 'cos': (dynamic x) => math.cos(_toNum(x) ?? 0), 42 | 'exp': (dynamic x) => math.exp(_toNum(x) ?? 0), 43 | 'max': (List args) { 44 | // Ensure all values are numbers and filter out nulls; then find the max value 45 | var numbers = args.map(_toNum).whereType().toList(); 46 | if (numbers.isEmpty) return double.negativeInfinity; 47 | return numbers.reduce(math.max); 48 | }, 49 | 'min': (List args) { 50 | // Ensure all values are numbers and filter out nulls; then find the min value 51 | var numbers = args.map(_toNum).whereType().toList(); 52 | if (numbers.isEmpty) return double.infinity; 53 | return numbers.reduce(math.min); 54 | }, 55 | 'sin': (dynamic x) => math.sin(_toNum(x) ?? 0), 56 | 'sqrt': (List args) { 57 | num val = 0; 58 | if (args.length > 0) { 59 | val = _toNum(args[0]); 60 | } 61 | return val.isNegative ? double.nan : math.sqrt(val); 62 | }, 63 | 'tan': (dynamic x) => math.tan(_toNum(x) ?? 0), 64 | 'random': () => math.Random().nextDouble(), 65 | }; 66 | } 67 | 68 | @override 69 | Map setters() { 70 | return {}; 71 | } 72 | 73 | @override 74 | callMethod(String methodName, List args) { 75 | Map _methods = methods(); 76 | if (methodName == 'max' || methodName == 'min' || methodName == 'sqrt') { 77 | // Special handling for max and min to support multiple arguments. 78 | return _methods[methodName]!(args); 79 | } else if (_methods.containsKey(methodName)) { 80 | if (args.length > 0) { 81 | return Function.apply(_methods[methodName]!, args); 82 | } else { 83 | if (methodName == 'random') { 84 | return Function.apply(_methods[methodName]!, null); 85 | } 86 | return Function.apply(_methods[methodName]!, [null]); 87 | } 88 | } else { 89 | throw 'Method not found'; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /lib/invokables/invokableprimitives.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui'; 3 | 4 | import 'package:duration/locale.dart'; 5 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 6 | import 'package:flutter/widgets.dart'; 7 | import 'package:intl/intl.dart'; 8 | import 'package:duration/duration.dart' as lib; 9 | 10 | abstract class InvokablePrimitive { 11 | static String prettyCurrency(dynamic input) { 12 | num? value; 13 | if (input is num) { 14 | value = input; 15 | } else if (input is String) { 16 | value = num.tryParse(input); 17 | } 18 | if (value != null) { 19 | NumberFormat formatter = NumberFormat.currency(locale: 'en_US', symbol: "\$"); 20 | return formatter.format(value); 21 | } 22 | return ''; 23 | } 24 | /// input should be # of seconds 25 | static String prettyDuration(dynamic input, {Locale? locale}) { 26 | if (input == null) { 27 | return ''; 28 | } 29 | if (input !is int) { 30 | input = int.tryParse(input.toString()); 31 | } 32 | 33 | String localeString = locale?.languageCode ?? 'nl'; // ?? Intl.getCurrentLocale().substring(0, 2); 34 | return lib.prettyDuration( 35 | Duration(seconds: input), 36 | abbreviated: false, 37 | upperTersity: lib.DurationTersity.week, 38 | tersity: lib.DurationTersity.minute, 39 | locale: DurationLocale.fromLanguageCode(localeString) ?? EnglishDurationLocale() 40 | ); 41 | } 42 | static String prettyDate(dynamic input) { 43 | DateTime? dateTime = parseDateTime(input)?.toLocal(); 44 | if (dateTime != null) { 45 | return DateFormat.yMMMd().format(dateTime); 46 | } 47 | return ''; 48 | } 49 | static String prettyDateTime(dynamic input) { 50 | DateTime? dateTime = parseDateTime(input)?.toLocal(); 51 | if (dateTime != null) { 52 | return DateFormat.yMMMd().format(dateTime) + ' ' + DateFormat.jm().format(dateTime); 53 | } 54 | return ''; 55 | } 56 | static String prettyTime(dynamic input) { 57 | DateTime? dateTime = parseDateTime(input)?.toLocal(); 58 | if (dateTime != null) { 59 | return DateFormat.jm().format(dateTime); 60 | } 61 | return ''; 62 | } 63 | /// try to parse the input into a DateTime. 64 | /// The returned DateTime is in UTC/GMT timezone (not your local DateTime) 65 | static DateTime? parseDateTime(dynamic input) { 66 | if (input is int) { 67 | return DateTime.fromMillisecondsSinceEpoch(input * 1000); 68 | } else if (input is String) { 69 | int? intValue = int.tryParse(input); 70 | if (intValue != null) { 71 | return DateTime.fromMillisecondsSinceEpoch(intValue * 1000); 72 | } else { 73 | // try parse ISO format 74 | try { 75 | return DateTime.parse(input); 76 | } on FormatException catch (_, e) {} 77 | 78 | // try http date format e.g Thu, 23 Jan 2022 05:05:05 GMT+0200 79 | 80 | // Note that HttpDate parser won't work if we have an offset e.g GMT+0700 or GMT -02:00 81 | // we'll remove the offset, parse the date, then adjust manually afterward 82 | String updatedInput = input; 83 | Duration? offset; 84 | bool isNegativeOffset = false; 85 | RegExpMatch? match = RegExp(r'.+ GMT(?\s?([+-])(\d{2}):?(\d{2}))$').firstMatch(input); 86 | if (match != null) { 87 | // remove any offset from the string so we can parse it 88 | String rawOffset = match.namedGroup('offset')!; 89 | updatedInput = updatedInput.substring(0, input.indexOf(rawOffset)); 90 | //print(updatedInput); 91 | 92 | isNegativeOffset = match.group(2) == '-' ? true : false; 93 | int hourOffset = int.parse(match.group(3)!); 94 | int minuteOffset = int.parse(match.group(4)!); 95 | 96 | offset = Duration(hours: hourOffset, minutes: minuteOffset); 97 | } 98 | try { 99 | DateTime gmtDateTime = HttpDate.parse(updatedInput); 100 | // adjust the offset 101 | if (offset != null) { 102 | gmtDateTime = isNegativeOffset ? gmtDateTime.add(offset) : gmtDateTime.subtract(offset); 103 | //print(parsedDateTime); 104 | } 105 | // we parsed the date as GMT, need to convert to our local time 106 | return gmtDateTime; 107 | } catch (e) {} 108 | 109 | } 110 | } 111 | return null; 112 | } 113 | 114 | 115 | dynamic getValue(); 116 | 117 | } -------------------------------------------------------------------------------- /lib/invokables/invokableregexp.dart: -------------------------------------------------------------------------------- 1 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 2 | 3 | class InvokableRegExp extends Object with Invokable { 4 | RegExp regExp; 5 | InvokableRegExp(this.regExp); 6 | @override 7 | Map getters() { 8 | // TODO: implement getters 9 | throw UnimplementedError(); 10 | } 11 | 12 | @override 13 | Map methods() { 14 | return { 15 | 'test': (String input) => regExp.hasMatch(input), 16 | //'exec': (String input) => regExp.allMatches(input).toList() 17 | 18 | }; 19 | } 20 | 21 | @override 22 | Map setters() { 23 | // TODO: implement setters 24 | throw UnimplementedError(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /lib/invokables/invokabletext.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 5 | 6 | class InvokableText extends StatefulWidget with Invokable, HasController { 7 | @override 8 | final TextController controller; 9 | InvokableText(this.controller, {Key? key}) : super(key:key); 10 | 11 | @override 12 | State createState() => InvokableTextWidgetState(); 13 | 14 | void toUppercase() { 15 | setters()['text']!(getters()['text']!().toString().toUpperCase()); 16 | } 17 | int random(int seed,int max) { 18 | return Random(seed).nextInt(max); 19 | } 20 | 21 | @override 22 | Map getters() { 23 | return { 24 | 'text': () => controller.text 25 | }; 26 | } 27 | 28 | @override 29 | Map setters() { 30 | return { 31 | 'text': (newValue) => controller.text = newValue, 32 | }; 33 | } 34 | 35 | @override 36 | Map methods() { 37 | return { 38 | 'random': (int seed,int max) { return random(seed,max);}, 39 | 'toUpperCase': () => toUppercase(), 40 | 'indexOf': (String str) { return controller.text.indexOf(str);} 41 | }; 42 | } 43 | 44 | 45 | } 46 | 47 | class TextController extends Controller { 48 | String text; 49 | TextController(this.text); 50 | } 51 | 52 | class InvokableTextWidgetState extends BaseWidgetState { 53 | @override 54 | Widget build(BuildContext context) { 55 | return Text(widget.controller.text); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/invokables/invokabletextformfield.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; 5 | 6 | class InvokableTextFormField extends TextFormField with Invokable { 7 | InvokableTextFormField({ 8 | Key? key, 9 | TextEditingController? controller, 10 | InputDecoration? decoration 11 | }) : super(key: key,controller:controller,decoration:decoration); 12 | 13 | void toUppercase() { 14 | setters()['value']!(getters()['value']!().toString().toUpperCase()); 15 | } 16 | int random(int seed,int max) { 17 | return Random(seed).nextInt(max); 18 | } 19 | 20 | @override 21 | Map getters() { 22 | return { 23 | 'value': () => controller!.value.text 24 | }; 25 | } 26 | 27 | @override 28 | Map methods() { 29 | return { 30 | 'random': (int seed,int max) { return random(seed,max);}, 31 | 'toUpperCase': () => toUppercase(), 32 | 'indexOf': (String str) { return controller!.value.text.indexOf(str);} 33 | }; 34 | } 35 | 36 | @override 37 | Map setters() { 38 | return { 39 | 'value': (newValue) => controller!.text = newValue, 40 | }; 41 | } 42 | 43 | @override 44 | dynamic getProperty(dynamic prop) { 45 | Function? f = getters()[prop]; 46 | if ( f != null ) { 47 | return f(); 48 | } 49 | throw Exception('getter by name='+prop+' is not defined on this object='+toString()); 50 | } 51 | 52 | @override 53 | void setProperty(dynamic prop, dynamic val) { 54 | Function? f = setters()[prop]; 55 | if ( f != null ) { 56 | f(val); 57 | } 58 | } 59 | 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:ensemble_ts_interpreter/view.dart'; 2 | import 'package:flutter/widgets.dart' hide View; 3 | 4 | class Layout { 5 | final Map map; 6 | final View view; 7 | Layout(this.map,this.view); 8 | static Layout from(Map map,View view) { 9 | return Layout(map,view); 10 | } 11 | Widget build(BuildContext context) { 12 | Widget rtn = const Text('hey'); 13 | map.forEach((k,v) { 14 | if (k == "Form") { 15 | rtn = buildForm(v as Map, context); 16 | } else if ( k == 'Expanded' ) { 17 | 18 | } 19 | }); 20 | return rtn; 21 | } 22 | Expanded buildExpanded(Map props,BuildContext context) { 23 | if ( props['items'] == null ) { 24 | throw Exception('Expanded must have items property'); 25 | } 26 | List childWidgets = buildChildWidgets(props['items'] as List); 27 | Widget child; 28 | if ( childWidgets.isNotEmpty ) { 29 | child = childWidgets.first; 30 | } else { 31 | throw Exception("Expanded must have one child"); 32 | } 33 | return Expanded(child:child); 34 | } 35 | Form buildForm(Map props,BuildContext context) { 36 | if ( props['items'] == null ) { 37 | throw Exception('Form must have items property'); 38 | } 39 | List childWidgets = buildChildWidgets(props['items'] as List); 40 | return Form(child:Column(children:childWidgets)); 41 | } 42 | List buildChildWidgets(List children) { 43 | List childWidgets = []; 44 | for ( final String id in children) { 45 | WidgetView? wv = view.get(id); 46 | if ( wv != null ) { 47 | childWidgets.add(wv.widget); 48 | } 49 | } 50 | return childWidgets; 51 | } 52 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:http/http.dart' as http; 3 | import 'package:ensemble_ts_interpreter/action.dart'; 4 | import 'package:ensemble_ts_interpreter/api.dart'; 5 | import 'package:ensemble_ts_interpreter/layout.dart'; 6 | import 'package:flutter/material.dart' hide View; 7 | import 'package:yaml/yaml.dart'; 8 | import 'view.dart'; 9 | 10 | void main() { 11 | runApp(const MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | const MyApp({Key? key}) : super(key: key); 16 | static const String _title = 'Ensemble Demo'; 17 | 18 | // This widget is the root of your application. 19 | @override 20 | Widget build(BuildContext context) { 21 | /* 22 | return MaterialApp( 23 | title: 'Flutter Demo', 24 | home: Scaffold( 25 | appBar: AppBar(title: const Text(_title)), 26 | body: const MyStatefulWidget(), 27 | ), 28 | ); 29 | */ 30 | return const MyStatefulWidget(); 31 | } 32 | } 33 | /// This is the stateful widget that the main application instantiates. 34 | class MyStatefulWidget extends StatefulWidget { 35 | const MyStatefulWidget({Key? key}) : super(key: key); 36 | 37 | @override 38 | State createState() => _MyStatefulWidgetState(); 39 | } 40 | Widget buildFrom(YamlMap doc,BuildContext context) { 41 | late Widget rtn = const Text('did not work'); 42 | if (doc['View'] != null) { 43 | View v = View.from(doc['View']); 44 | Map apis = APIs.from(doc['APIs']); 45 | List actions = EnsembleActions.configure( 46 | v, apis, doc['Actions']); 47 | Layout l = Layout.from(doc["Layout"], v); 48 | rtn = l.build(context); 49 | } 50 | return rtn; 51 | } 52 | /// This is the private State class that goes with MyStatefulWidget. 53 | class _MyStatefulWidgetState extends State { 54 | final GlobalKey _formKey = GlobalKey(); 55 | 56 | Future loadAsset(BuildContext context, String name) async { 57 | return await DefaultAssetBundle.of(context).loadString('assets/' + name); 58 | } 59 | 60 | 61 | final List entries = ['Sample1: Guess Gender from Name', 'Sample2: Stock Quotes', 'Sample3: Web View of news']; 62 | final List entryPaths = ['a0dcfefc-298d-42ab-932a-aab70c87891b', '0b66a9d9-9125-46b3-82ec-5b360ab73fbd','c8a19fcf-69c0-4751-9733-032291c83937']; 63 | final List colorCodes = [600, 500]; 64 | @override 65 | Widget build(BuildContext context) { 66 | return MaterialApp( 67 | title: 'Server Driven UI', 68 | home: Scaffold( 69 | appBar: AppBar(title: const Text("Server Driven UI Demo")), 70 | body: Container( 71 | child:Center( 72 | child: Column( 73 | children:[ 74 | Flexible( 75 | child: ListView.separated( 76 | shrinkWrap: true, 77 | padding: const EdgeInsets.all(8), 78 | itemCount: entries.length, 79 | itemBuilder: (BuildContext context, int index) { 80 | return GestureDetector( 81 | onTap: () { 82 | GoToPage(entryPaths[index],entries[index],context); 83 | }, 84 | child: SizedBox( 85 | height: 50, 86 | child: Center( 87 | child: Text(entries[index], 88 | style: const TextStyle(fontWeight: FontWeight.bold)), 89 | ) 90 | )); 91 | }, 92 | separatorBuilder: (BuildContext context, int index) => const Divider(), 93 | ) 94 | ), 95 | const Padding( 96 | padding: EdgeInsets.symmetric(vertical:50,horizontal:10), 97 | child:Text("Above are just three examples of Server Driven UI. " 98 | "\n\nTapping on each will make a request to the sever and fetch the definition of the page alongwith some simple UI logic. \n\nThis the ViewModel. \n\nThis app will parse the ViewModel and create the page dynamically. " 99 | "\n\nThe view definition also specifies action handlers, APIs to call and simple logic to use. " 100 | "\n\nEnjoy :-) email me at khuram.mahmood@gmail.com if you have questions. " 101 | "\n\n\n\nGender API is a free API provided by https://gender-api.com while Stock API is a free API provided by https://www.alphavantage.co/", 102 | )) 103 | ]))) 104 | )); 105 | } 106 | } 107 | 108 | /* 109 | @override 110 | Widget build(BuildContext context) { 111 | return MaterialApp( 112 | title: 'Flutter Demo', 113 | home: Scaffold( 114 | appBar: AppBar(title: const Text("Hello")), 115 | body: FutureBuilder( 116 | future:loadAsset(context,'basic_conditionals.yaml'), 117 | builder:(BuildContext context,AsyncSnapshot snapShot) { 118 | Column rtn = Column(); 119 | if (snapShot.hasData) { 120 | YamlMap doc = loadYaml(snapShot.requireData); 121 | rtn = Column( 122 | crossAxisAlignment: CrossAxisAlignment.start, 123 | children: [buildFrom(doc)] 124 | ); 125 | } 126 | return rtn; 127 | } 128 | ) 129 | ) 130 | ); 131 | */ 132 | 133 | class SecondRoute extends StatelessWidget { 134 | String? definition,title; 135 | SecondRoute({Key? key}) : super(key: key); 136 | 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | YamlMap doc = loadYaml(definition!); 141 | return Scaffold( 142 | appBar: AppBar( 143 | title: Text(title!), 144 | ), 145 | body: Column ( 146 | crossAxisAlignment: CrossAxisAlignment.start, 147 | children: [buildFrom(doc,context)] 148 | ) 149 | /* 150 | child: ElevatedButton( 151 | onPressed: () { 152 | Navigator.pop(context); 153 | }, 154 | child: const Text('Go back!'), 155 | ),*/ 156 | ); 157 | } 158 | } 159 | Future GoToPage(page,title,context) async { 160 | final response = await http 161 | .get(Uri.parse('https://pz0mwfkp5m.execute-api.us-east-1.amazonaws.com/dev/screen/content?id=$page')); 162 | 163 | if (response.statusCode == 200) { 164 | // If the server did return a 200 OK response, 165 | // then parse the JSON. 166 | SecondRoute r = SecondRoute(); 167 | r.definition = response.body; 168 | r.title = title; 169 | Navigator.push( 170 | context, 171 | MaterialPageRoute(builder: (context) => r), 172 | ); 173 | return response.body; 174 | } else { 175 | // If the server did not return a 200 OK response, 176 | // then throw an exception. 177 | throw Exception('Failed to load album'); 178 | } 179 | } -------------------------------------------------------------------------------- /lib/parser/ast.dart: -------------------------------------------------------------------------------- 1 | //http://160.16.109.33/github.com/mason-lang/esast/ 2 | import 'package:yaml/yaml.dart'; 3 | 4 | abstract class JSASTVisitor { 5 | void visitExpressionStatement(ExpressionStatement stmt); 6 | void visitAssignmentExpression(AssignmentExpression stmt); 7 | dynamic visitThisExpression(ThisExpr stmt); 8 | dynamic visitArrayExpression(ArrayExpression stmt); 9 | dynamic visitMemberExpression(MemberExpr stmt); 10 | void visitIfStatement(IfStatement stmt); 11 | dynamic visitConditionalExpression(ConditionalExpression stmt); 12 | dynamic visitBinaryExpression(BinaryExpression stmt); 13 | bool visitLogicalExpression(LogicalExpression stmt); 14 | dynamic visitUnaryExpression(UnaryExpression stmt); 15 | dynamic visitLiteral(Literal stmt); 16 | String visitIdentifier(Identifier stmt); 17 | dynamic visitBlockStatement(BlockStatement stmt); 18 | dynamic visitCallExpression(CallExpression stmt); 19 | Function visitArrowFunctionExpression(ArrowFunctionExpression stmt); 20 | void visitVariableDeclaration(VariableDeclaration stmt); 21 | void visitVariableDeclarator(VariableDeclarator stmt); 22 | Map visitObjectExpression(ObjectExpr stmt); 23 | Map visitProperty(Property stmt); 24 | dynamic visitReturnStatement(ReturnStatement stmt); 25 | dynamic visitExpression(Expression stmt) { 26 | if ( stmt is BinaryExpression ) { 27 | return visitBinaryExpression(stmt); 28 | } else if ( stmt is LogicalExpression ) { 29 | return visitLogicalExpression(stmt); 30 | } else if ( stmt is CallExpression ) { 31 | return visitCallExpression(stmt); 32 | } else if ( stmt is MemberExpr ) { 33 | return visitMemberExpression(stmt); 34 | } else if ( stmt is AssignmentExpression ) { 35 | return visitAssignmentExpression(stmt); 36 | } else if ( stmt is Identifier ) { 37 | return visitIdentifier(stmt); 38 | } else if ( stmt is Literal ) { 39 | return visitLiteral(stmt); 40 | } else if ( stmt is UnaryExpression ) { 41 | return visitUnaryExpression(stmt); 42 | } else if ( stmt is ArrowFunctionExpression ) { 43 | return visitArrowFunctionExpression(stmt); 44 | } else if (stmt is ThisExpr) { 45 | return visitThisExpression(stmt); 46 | } else if (stmt is ConditionalExpression) { 47 | return visitConditionalExpression(stmt); 48 | } else if ( stmt is ArrayExpression ) { 49 | return visitArrayExpression(stmt); 50 | } else if ( stmt is ObjectExpr ) { 51 | return visitObjectExpression(stmt); 52 | } else { 53 | throw Exception("This type of expression is not currently supported. Expression="+stmt.toString()); 54 | } 55 | } 56 | 57 | } 58 | enum BinaryOperator { 59 | equals,lt,gt,ltEquals,gtEquals,notequals,minus,plus,multiply,divide,inop,instaneof 60 | } 61 | enum AssignmentOperator { 62 | equal,plusEqual,minusEqual 63 | } 64 | enum LogicalOperator { 65 | or,and,not 66 | } 67 | enum UnaryOperator { 68 | minus,plus,not,typeof,voidop 69 | } 70 | enum VariableDeclarationKind { 71 | constant,let,variable 72 | } 73 | abstract class ASTNode { 74 | dynamic accept(JSASTVisitor visitor); 75 | } 76 | class IfStatement implements ASTNode { 77 | Expression test; 78 | ASTNode consequent; 79 | ASTNode? alternate; 80 | IfStatement(this.test,this.consequent,this.alternate); 81 | static IfStatement fromJson(var jsonNode,ASTBuilder builder) { 82 | return IfStatement(builder.buildNode(jsonNode['test']) as Expression, 83 | builder.buildNode(jsonNode['consequent']), 84 | (jsonNode['alternate']!=null)?builder.buildNode(jsonNode['alternate']):null 85 | ); 86 | } 87 | @override 88 | dynamic accept(JSASTVisitor visitor) { 89 | return visitor.visitIfStatement(this); 90 | } 91 | } 92 | class ConditionalExpression implements Expression { 93 | Expression test,consequent,alternate; 94 | ConditionalExpression(this.test,this.consequent,this.alternate); 95 | static ConditionalExpression fromJson(var jsonNode,ASTBuilder builder) { 96 | return ConditionalExpression(builder.buildNode(jsonNode['test']) as Expression, 97 | builder.buildNode(jsonNode['consequent']) as Expression, 98 | builder.buildNode(jsonNode['alternate']) as Expression); 99 | } 100 | @override 101 | accept(JSASTVisitor visitor) { 102 | return visitor.visitConditionalExpression(this); 103 | } 104 | 105 | } 106 | class BlockStatement implements ASTNode { 107 | List statements; 108 | BlockStatement(this.statements); 109 | static BlockStatement fromJson(var jsonNode,ASTBuilder builder) { 110 | List nodes = []; 111 | List stmts = jsonNode['body']; 112 | stmts.forEach((node) { 113 | nodes.add(builder.buildNode(node)); 114 | }); 115 | return BlockStatement(nodes); 116 | } 117 | @override 118 | dynamic accept(JSASTVisitor visitor) { 119 | return visitor.visitBlockStatement(this); 120 | } 121 | } 122 | abstract class Expression extends ASTNode {} 123 | abstract class BooleanExpression extends Expression {} 124 | class UnaryExpression implements Expression { 125 | Expression argument; 126 | UnaryOperator op; 127 | UnaryExpression(this.argument,this.op); 128 | static UnaryExpression fromJson(var jsonNode,ASTBuilder builder) { 129 | String operator = jsonNode['operator']; 130 | UnaryOperator? op; 131 | if ( operator == '-' ) { 132 | op = UnaryOperator.minus; 133 | } else if ( operator == '+' ) { 134 | op = UnaryOperator.plus; 135 | } else if ( operator == '!' ) { 136 | op = UnaryOperator.not; 137 | } else if ( operator == 'typeof' ) { 138 | op = UnaryOperator.typeof; 139 | } else if ( operator == 'void' ) { 140 | op = UnaryOperator.voidop; 141 | } else { 142 | Exception(operator+' is not yet supported'); 143 | } 144 | return UnaryExpression(builder.buildNode(jsonNode['argument']) as Expression, op!); 145 | } 146 | 147 | @override 148 | dynamic accept(JSASTVisitor visitor) { 149 | return visitor.visitUnaryExpression(this); 150 | } 151 | 152 | } 153 | 154 | class BinaryExpression implements BooleanExpression { 155 | Expression left,right; 156 | BinaryOperator op; 157 | BinaryExpression(this.left,this.op,this.right); 158 | static BinaryExpression fromJson(var jsonNode,ASTBuilder builder) { 159 | String operator = jsonNode['operator']; 160 | BinaryOperator? op; 161 | if ( operator == '==' ) { 162 | op = BinaryOperator.equals; 163 | } else if ( operator == '<' ) { 164 | op = BinaryOperator.lt; 165 | } else if ( operator == '<=' ) { 166 | op = BinaryOperator.ltEquals; 167 | } else if ( operator == '>' ) { 168 | op = BinaryOperator.gt; 169 | } else if ( operator == '<=' ) { 170 | op = BinaryOperator.gtEquals; 171 | } else if ( operator == '!=' ) { 172 | op = BinaryOperator.notequals; 173 | } else if ( operator == '-' ) { 174 | op = BinaryOperator.minus; 175 | } else if ( operator == '+' ) { 176 | op = BinaryOperator.plus; 177 | } else if ( operator == '*' ) { 178 | op = BinaryOperator.multiply; 179 | } else if ( operator == '/' ) { 180 | op = BinaryOperator.divide; 181 | } else { 182 | Exception(operator+' is not yet supported'); 183 | } 184 | return BinaryExpression(builder.buildNode(jsonNode['left']) as Expression, 185 | op!, 186 | builder.buildNode(jsonNode['right']) as Expression 187 | ); 188 | } 189 | @override 190 | dynamic accept(JSASTVisitor visitor) { 191 | return visitor.visitBinaryExpression(this); 192 | } 193 | } 194 | abstract class Declaration extends ASTNode {} 195 | class VariableDeclaration implements Declaration { 196 | VariableDeclarationKind kind; 197 | List declarators; 198 | VariableDeclaration(this.kind,this.declarators); 199 | static VariableDeclaration fromJson(var jsonNode,ASTBuilder builder) { 200 | String k = jsonNode['kind'] as String; 201 | VariableDeclarationKind kind; 202 | if ( k == 'let' ) { 203 | kind = VariableDeclarationKind.let; 204 | } else if ( k == 'var' ) { 205 | kind = VariableDeclarationKind.variable; 206 | } else { 207 | kind = VariableDeclarationKind.constant; 208 | } 209 | List declarations = jsonNode['declarations']; 210 | List declarators = []; 211 | for ( var node in declarations ) { 212 | declarators.add(builder.buildNode(node) as VariableDeclarator); 213 | } 214 | return VariableDeclaration(kind,declarators); 215 | } 216 | @override 217 | accept(JSASTVisitor visitor) { 218 | return visitor.visitVariableDeclaration(this); 219 | } 220 | } 221 | class VariableDeclarator extends ASTNode { 222 | Identifier id; 223 | Expression? init; 224 | VariableDeclarator(this.id,this.init); 225 | static VariableDeclarator fromJson(var jsonNode,ASTBuilder builder) { 226 | ASTNode n = builder.buildNode(jsonNode['id']); 227 | if ( n is! Identifier ) { 228 | throw Exception('Only Identifiers are supported for variable declarations at this time'); 229 | } 230 | Identifier id = n; 231 | Expression? init; 232 | if ( jsonNode['init'] != null ) { 233 | init = builder.buildNode(jsonNode['init']) as Expression; 234 | } 235 | return VariableDeclarator(id,init); 236 | } 237 | @override 238 | accept(JSASTVisitor visitor) { 239 | return visitor.visitVariableDeclarator(this); 240 | } 241 | } 242 | class ArrowFunctionExpression implements Expression { 243 | BlockStatement? blockStmt; 244 | Expression? expression; 245 | List params; 246 | ArrowFunctionExpression(this.blockStmt,this.expression,this.params); 247 | static ArrowFunctionExpression fromJson(var jsonNode,ASTBuilder builder) { 248 | List params = builder.buildArray(jsonNode['params']); 249 | BlockStatement? blockStmt; 250 | Expression? expression; 251 | if ( jsonNode['body']['type'] == 'BlockStatement' ) { 252 | blockStmt = builder.buildNode(jsonNode['body']) as BlockStatement; 253 | } else { 254 | expression = builder.buildNode(jsonNode['body']) as Expression; 255 | } 256 | return ArrowFunctionExpression(blockStmt,expression,params); 257 | } 258 | @override 259 | accept(JSASTVisitor visitor) { 260 | return visitor.visitArrowFunctionExpression(this); 261 | } 262 | 263 | } 264 | //http://160.16.109.33/github.com/mason-lang/esast/class/src/ast.js~CallExpression.html 265 | class CallExpression implements Expression { 266 | Expression callee; 267 | List arguments; 268 | CallExpression(this.callee,this.arguments); 269 | static CallExpression fromJson(var jsonNode,ASTBuilder builder) { 270 | Expression callee = builder.buildNode(jsonNode['callee']) as Expression; 271 | return CallExpression(callee, builder.buildArray(jsonNode['arguments'])); 272 | } 273 | @override 274 | dynamic accept(JSASTVisitor visitor) { 275 | return visitor.visitCallExpression(this); 276 | } 277 | } 278 | class LogicalExpression implements BooleanExpression { 279 | Expression left,right; 280 | LogicalOperator op; 281 | LogicalExpression(this.left,this.op,this.right); 282 | static LogicalExpression fromJson(var jsonNode,ASTBuilder builder) { 283 | String operator = jsonNode['operator']; 284 | LogicalOperator? op; 285 | if ( operator == '&&' ) { 286 | op = LogicalOperator.and; 287 | } else if ( operator == '||' ) { 288 | op = LogicalOperator.or; 289 | } else if ( operator == '|' ) { 290 | op = LogicalOperator.not; 291 | } else { 292 | Exception(operator+' is not yet supported'); 293 | } 294 | Expression left = builder.buildNode(jsonNode['left']) as Expression; 295 | return LogicalExpression(builder.buildNode(jsonNode['left']) as Expression, 296 | op!, 297 | builder.buildNode(jsonNode['right']) as Expression 298 | ); 299 | } 300 | @override 301 | dynamic accept(JSASTVisitor visitor) { 302 | return visitor.visitLogicalExpression(this); 303 | } 304 | } 305 | class Literal implements Expression { 306 | dynamic value; 307 | Literal(this.value); 308 | static Literal fromJson(var jsonNode,ASTBuilder builder) { 309 | return Literal(jsonNode['value']); 310 | } 311 | @override 312 | dynamic accept(JSASTVisitor visitor) { 313 | return visitor.visitLiteral(this); 314 | } 315 | } 316 | class Identifier implements Expression { 317 | String name; 318 | Identifier(this.name); 319 | static Identifier fromJson(var jsonNode,ASTBuilder builder) { 320 | return Identifier(jsonNode['name']); 321 | } 322 | @override 323 | accept(JSASTVisitor visitor) { 324 | return visitor.visitIdentifier(this); 325 | } 326 | } 327 | class ExpressionStatement implements ASTNode { 328 | ASTNode expression; 329 | ExpressionStatement(this.expression); 330 | static ExpressionStatement fromJson(var jsonNode,ASTBuilder builder) { 331 | var exp = jsonNode['expression']; 332 | return ExpressionStatement(builder.buildNode(exp)); 333 | } 334 | @override 335 | dynamic accept(JSASTVisitor visitor) { 336 | return visitor.visitExpressionStatement(this); 337 | } 338 | } 339 | 340 | class AssignmentExpression implements Expression { 341 | Expression left,right; 342 | AssignmentOperator op; 343 | AssignmentExpression(this.left,this.op,this.right); 344 | static AssignmentExpression fromJson(var jsonNode,ASTBuilder builder) { 345 | AssignmentOperator op; 346 | if ( jsonNode['operator'] == '=' ) { 347 | op = AssignmentOperator.equal; 348 | } else if ( jsonNode['operator'] == '+=' ) { 349 | op = AssignmentOperator.plusEqual; 350 | } else if ( jsonNode['operator'] == '-=' ) { 351 | op = AssignmentOperator.minusEqual; 352 | } else { 353 | throw Exception('Operator '+jsonNode['operator']+' is not yet supported'); 354 | } 355 | return AssignmentExpression(builder.buildNode(jsonNode['left']) as Expression, 356 | op, builder.buildNode(jsonNode['right']) as Expression); 357 | } 358 | @override 359 | accept(JSASTVisitor visitor) { 360 | return visitor.visitAssignmentExpression(this); 361 | } 362 | } 363 | 364 | class ThisExpr implements Expression { 365 | ThisExpr(); 366 | static ThisExpr fromJson(var jsonNode, ASTBuilder builder) { 367 | return ThisExpr(); 368 | } 369 | @override 370 | accept(JSASTVisitor visitor) { 371 | return visitor.visitThisExpression(this); 372 | } 373 | } 374 | class ArrayExpression implements Expression { 375 | List arr; 376 | ArrayExpression(this.arr); 377 | static ArrayExpression fromJson(var jsonNode,ASTBuilder builder) { 378 | return ArrayExpression(builder.buildArray(jsonNode['elements'] as List)); 379 | } 380 | @override 381 | accept(JSASTVisitor visitor) { 382 | return visitor.visitArrayExpression(this); 383 | } 384 | } 385 | class Property extends ASTNode { 386 | Expression key;//either Literal | Identifier 387 | Expression value; 388 | Property(this.key,this.value); 389 | static Property fromJson(var jsonNode,ASTBuilder builder) { 390 | if ( jsonNode['kind'] != 'init' ) { 391 | throw Exception('currently function calls to initialize properties of an object are unsupported'); 392 | } 393 | Expression key = builder.buildNode(jsonNode['key']) as Expression; 394 | if ( !(key is Literal) && !(key is Identifier) ) { 395 | throw Exception("Property key must be either Literal or Identifier"); 396 | } 397 | return Property(key,builder.buildNode(jsonNode['value']) as Expression); 398 | } 399 | 400 | @override 401 | accept(JSASTVisitor visitor) { 402 | return visitor.visitProperty(this); 403 | } 404 | } 405 | class ObjectExpr implements Expression { 406 | List properties; 407 | ObjectExpr(this.properties); 408 | static ObjectExpr fromJson(var jsonNode,ASTBuilder builder) { 409 | List props = jsonNode['properties']; 410 | return ObjectExpr(buildArray(props,builder)); 411 | } 412 | 413 | @override 414 | accept(JSASTVisitor visitor) { 415 | return visitor.visitObjectExpression(this); 416 | } 417 | static List buildArray(var jsonArr,ASTBuilder builder) { 418 | List nodes = []; 419 | jsonArr.forEach((node) { 420 | nodes.add(builder.buildNode(node) as Property); 421 | }); 422 | return nodes; 423 | } 424 | } 425 | class MemberExpr implements Expression { 426 | Expression object,property; 427 | MemberExpr(this.object,this.property); 428 | static MemberExpr fromJson(var jsonNode,ASTBuilder builder) { 429 | return MemberExpr(builder.buildNode(jsonNode['object']) as Expression, builder.buildNode(jsonNode['property']) as Expression); 430 | } 431 | @override 432 | accept(JSASTVisitor visitor) { 433 | return visitor.visitMemberExpression(this); 434 | } 435 | } 436 | class ReturnStatement extends ASTNode { 437 | Expression? argument; 438 | ReturnStatement(this.argument); 439 | static ReturnStatement fromJson(var jsonNode,ASTBuilder builder) { 440 | Expression? argument; 441 | if ( jsonNode['argument'] != null ) { 442 | argument = builder.buildNode(jsonNode['argument']) as Expression; 443 | } 444 | return ReturnStatement(argument); 445 | } 446 | 447 | @override 448 | accept(JSASTVisitor visitor) { 449 | return visitor.visitReturnStatement(this); 450 | } 451 | } 452 | class ASTBuilder { 453 | List buildArray(var jsonArr) { 454 | List nodes = []; 455 | jsonArr.forEach((node) { 456 | nodes.add(buildNode(node)); 457 | }); 458 | return nodes; 459 | } 460 | /* 461 | List getBindableExpressionsAsStrings(List nodes) { 462 | List expStrs = []; 463 | List expressions = getBindableExpressions(nodes); 464 | for ( Expression exp : expressions ) { 465 | expStrs.add(exp.) 466 | } 467 | } 468 | List getBindableExpressionsFromNode(ASTNode node) { 469 | List exps = []; 470 | if ( node is BlockStatement ) { 471 | exps.addAll(getBindableExpressions(node.statements)); 472 | } else if ( node is AssignmentExpression ) { 473 | exps.addAll(getBindableExpressionsFromNode(node.right)); 474 | } else if ( node is BinaryExpression) { 475 | exps.addAll(getBindableExpressionsFromNode(node.left)); 476 | exps.addAll(getBindableExpressionsFromNode(node.right)); 477 | } else if ( node is LogicalExpression ) { 478 | exps.addAll(getBindableExpressionsFromNode(node.left)); 479 | exps.addAll(getBindableExpressionsFromNode(node.right)); 480 | } else if ( node is Expression ) { 481 | expToTest = node; 482 | } 483 | if ( expToTest is! Literal ) { 484 | exps.add(expToTest as Expression); 485 | } 486 | } 487 | 488 | */ 489 | 490 | ASTNode buildNode(var node) { 491 | String type = (node is Map)?node['type']:node.toString(); 492 | if ( type == 'ExpressionStatement' ) { 493 | return ExpressionStatement.fromJson(node, this); 494 | } else if ( type == 'AssignmentExpression' ) { 495 | return AssignmentExpression.fromJson(node,this); 496 | } else if ( type == 'MemberExpression' ) { 497 | return MemberExpr.fromJson(node, this); 498 | } else if ( type == 'IfStatement' ) { 499 | return IfStatement.fromJson(node, this); 500 | } else if ( type == 'ConditionalExpression' ) { 501 | return ConditionalExpression.fromJson(node, this); 502 | }else if ( type == 'Literal' ) { 503 | return Literal.fromJson(node, this); 504 | } else if ( type == 'Identifier' ) { 505 | return Identifier.fromJson(node, this); 506 | } else if ( type == 'BlockStatement' ) { 507 | return BlockStatement.fromJson(node, this); 508 | } else if ( type == 'BinaryExpression' ) { 509 | return BinaryExpression.fromJson(node, this); 510 | } else if ( type == 'LogicalExpression' ) { 511 | return LogicalExpression.fromJson(node, this); 512 | } else if ( type == 'CallExpression' ) { 513 | return CallExpression.fromJson(node, this); 514 | } else if ( type == 'UnaryExpression' ) { 515 | return UnaryExpression.fromJson(node, this); 516 | } else if (type == 'ThisExpression') { 517 | return ThisExpr.fromJson(node, this); 518 | } else if ( type == 'ArrayExpression' ) { 519 | return ArrayExpression.fromJson(node, this); 520 | } else if ( type == 'ArrowFunctionExpression' ) { 521 | return ArrowFunctionExpression.fromJson(node, this); 522 | } else if ( type == 'VariableDeclaration' ) { 523 | return VariableDeclaration.fromJson(node, this); 524 | } else if ( type == 'VariableDeclarator' ) { 525 | return VariableDeclarator.fromJson(node, this); 526 | } else if ( type == 'ObjectExpression' ) { 527 | return ObjectExpr.fromJson(node, this); 528 | } else if ( type == 'Property' ) { 529 | return Property.fromJson(node, this); 530 | } else if ( type == 'ReturnStatement' ) { 531 | return ReturnStatement.fromJson(node, this); 532 | } 533 | throw Exception(type+" is not yet supported. Full expression is="+node.toString()); 534 | } 535 | } -------------------------------------------------------------------------------- /lib/parser/find_bindables.dart: -------------------------------------------------------------------------------- 1 | import 'package:ensemble_ts_interpreter/parser/ast.dart'; 2 | 3 | class BindableExpressionFinder extends JSASTVisitor { 4 | List bindableExpressions = []; 5 | List nodes; 6 | Map context; 7 | BindableExpressionFinder(this.nodes,this.context); 8 | List findBindables() { 9 | for (ASTNode node in nodes) { 10 | node.accept(this); 11 | } 12 | return bindableExpressions; 13 | } 14 | @override 15 | visitArrayExpression(ArrayExpression stmt) { 16 | } 17 | 18 | @override 19 | Function visitArrowFunctionExpression(ArrowFunctionExpression stmt) { 20 | return ()=>''; 21 | } 22 | 23 | @override 24 | void visitAssignmentExpression(AssignmentExpression stmt) { 25 | stmt.right.accept(this); 26 | } 27 | 28 | @override 29 | visitBinaryExpression(BinaryExpression stmt) { 30 | stmt.left.accept(this); 31 | stmt.right.accept(this); 32 | } 33 | 34 | @override 35 | visitBlockStatement(BlockStatement stmt) { 36 | stmt.statements.forEach((element) => element.accept(this)); 37 | } 38 | 39 | @override 40 | visitCallExpression(CallExpression stmt) { 41 | } 42 | 43 | @override 44 | visitConditionalExpression(ConditionalExpression stmt) { 45 | stmt.test.accept(this); 46 | } 47 | 48 | @override 49 | void visitExpressionStatement(ExpressionStatement stmt) { 50 | stmt.expression.accept(this); 51 | } 52 | 53 | @override 54 | String visitIdentifier(Identifier stmt) { 55 | if ( context.containsKey(stmt.name) ) { 56 | bindableExpressions.add(stmt.name); 57 | } 58 | return ''; 59 | } 60 | 61 | @override 62 | void visitIfStatement(IfStatement stmt) { 63 | stmt.test.accept(this); 64 | } 65 | 66 | @override 67 | visitLiteral(Literal stmt) { 68 | } 69 | 70 | @override 71 | bool visitLogicalExpression(LogicalExpression stmt) { 72 | stmt.left.accept(this); 73 | stmt.right.accept(this); 74 | return true; 75 | } 76 | 77 | @override 78 | visitMemberExpression(MemberExpr stmt) { 79 | stmt.object.accept(this); 80 | } 81 | 82 | @override 83 | Map visitObjectExpression(ObjectExpr stmt) { 84 | return {}; 85 | } 86 | 87 | @override 88 | Map visitProperty(Property stmt) { 89 | return {}; 90 | } 91 | 92 | @override 93 | visitReturnStatement(ReturnStatement stmt) { 94 | if ( stmt.argument != null ) { 95 | stmt.argument!.accept(this); 96 | } 97 | } 98 | 99 | @override 100 | visitThisExpression(ThisExpr stmt) { 101 | } 102 | 103 | @override 104 | visitUnaryExpression(UnaryExpression stmt) { 105 | stmt.argument.accept(this); 106 | } 107 | 108 | @override 109 | void visitVariableDeclaration(VariableDeclaration stmt) { 110 | stmt.declarators.forEach((element) => element.accept(this)); 111 | } 112 | 113 | @override 114 | void visitVariableDeclarator(VariableDeclarator stmt) { 115 | if (stmt.init != null) { 116 | stmt.init!.accept(this); 117 | } 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /lib/view.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:collection'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:ensemble_ts_interpreter/invokables/invokabletext.dart'; 5 | import 'package:ensemble_ts_interpreter/invokables/invokabletextformfield.dart'; 6 | import 'package:yaml/yaml.dart'; 7 | 8 | class View { 9 | static const String PROPERTIES = "properties"; 10 | String title; 11 | List items; 12 | Map idWidgetMap; 13 | View(this.title,this.items,this.idWidgetMap); 14 | static View from(YamlMap map) { 15 | String title = map['title']; 16 | var list = map['items']; 17 | List items = []; 18 | Map idWidgetMap = HashMap(); 19 | if ( list != null ) { 20 | for ( final YamlMap item in list ) { 21 | //a. *Where are you going : Auto-complete 22 | item.forEach((k,v) { 23 | var arr = k.split('.'); 24 | String id = arr[0]; 25 | String label = arr[1]; 26 | WidgetView? wv = WidgetViewFactory.getWidgetView(v, id, label, null); 27 | if ( wv != null ) { 28 | items.add(wv); 29 | idWidgetMap[id] = wv; 30 | } 31 | }); 32 | } 33 | } 34 | return View(title,items,idWidgetMap); 35 | } 36 | WidgetView? get(String id) { 37 | if ( idWidgetMap.containsKey(id) ) { 38 | return idWidgetMap[id]; 39 | } 40 | return null; 41 | } 42 | WidgetView requireValue(String id) { 43 | if ( idWidgetMap.containsKey(id) ) { 44 | return idWidgetMap[id]!; 45 | } 46 | throw Exception(id+' is not present in the map'); 47 | } 48 | } 49 | class WidgetViewFactory { 50 | static WidgetView? getWidgetView(String name,String key,String label, Map? properties) { 51 | WidgetView? rtn; 52 | if ( name == 'TextInput' ) { 53 | /*TextFormField widget = TextFormField( 54 | key: Key(key), 55 | controller: TextEditingController(), 56 | decoration: InputDecoration(labelText:label,hintText:label), 57 | ); 58 | */ 59 | TextFormField widget = InvokableTextFormField( 60 | key: Key(key), 61 | controller: TextEditingController(), 62 | decoration: InputDecoration(labelText:label,hintText:label), 63 | ); 64 | rtn = WidgetView(widget, properties); 65 | } else if ( name == 'Button' ) { 66 | rtn = WidgetView( 67 | TextButton( 68 | key: Key(key), 69 | onPressed: () { }, 70 | child: Text(label) 71 | ),properties 72 | ); 73 | } else if ( name == 'Text' ) { 74 | rtn = WidgetView( 75 | InvokableText( 76 | TextController(label), 77 | key: Key(key) 78 | ),properties 79 | ); 80 | } 81 | return rtn; 82 | } 83 | } 84 | class WidgetView { 85 | Widget widget; 86 | final Map? properties; 87 | WidgetView(this.widget,this.properties); 88 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ensemble_ts_interpreter 2 | description: >- 3 | Javascript (ES5) Interpreter written entirely in Dart. 4 | Can be used in any Dart program or a Flutter app. 5 | Support basic javascript including primitive types, maps, lists and declaring and executing functions 6 | homepage: https://ensembleui.com 7 | repository: https://github.com/EnsembleUI/ensemble_ts_interpreter 8 | issue_tracker: https://github.com/EnsembleUI/ensemble_ts_interpreter/issues 9 | documentation: https://github.com/EnsembleUI/ensemble_ts_interpreter 10 | # The following line prevents the package from being accidentally published to 11 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 12 | # publish_to: 'none' # Remove this line if you wish to publish to pub.dev 13 | 14 | # The following defines the version and build number for your application. 15 | # A version number is three numbers separated by dots, like 1.2.43 16 | # followed by an optional build number separated by a +. 17 | # Both the version and the builder number may be overridden in flutter 18 | # build by specifying --build-name and --build-number, respectively. 19 | # In Android, build-name is used as versionName while build-number used as versionCode. 20 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 21 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 22 | # Read more about iOS versioning at 23 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 24 | version: 1.0.0+1 25 | 26 | environment: 27 | sdk: ">=2.13.0 <3.0.0" 28 | 29 | # Dependencies specify other packages that your package needs in order to work. 30 | # To automatically upgrade your package dependencies to the latest versions 31 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 32 | # dependencies can be manually updated by changing the version numbers below to 33 | # the latest version available on pub.dev. To see which dependencies have newer 34 | # versions available, run `flutter pub outdated`. 35 | dependencies: 36 | flutter: 37 | sdk: flutter 38 | 39 | # The following adds the Cupertino Icons font to your application. 40 | # Use with the CupertinoIcons class for iOS style icons. 41 | cupertino_icons: ^1.0.2 42 | yaml: ^3.1.0 43 | http: ^0.13.4 44 | json_path: ^0.5.2 45 | duration: ^3.0.11 46 | dart_date: 47 | git: 48 | url: https://github.com/spaznuski/dart_date.git 49 | ref: master 50 | #flutter_js: ^0.5.0+6 51 | #dartjsengine: ^1.0.1 52 | jsparser: 53 | git: 54 | url: https://github.com/EnsembleUI/parsejs_null_safety.dart 55 | ref: master 56 | source_span: ^1.9.1 57 | intl: ^0.18.0 58 | 59 | dev_dependencies: 60 | flutter_test: 61 | sdk: flutter 62 | 63 | # The "flutter_lints" package below contains a set of recommended lints to 64 | # encourage good coding practices. The lint set provided by the package is 65 | # activated in the `analysis_options.yaml` file located at the root of your 66 | # package. See that file for information about deactivating specific lint 67 | # rules and activating additional ones. 68 | flutter_lints: ^2.0.1 69 | test: 70 | 71 | # For information on the generic Dart part of this file, see the 72 | # following page: https://dart.dev/tools/pub/pubspec 73 | 74 | # The following section is specific to Flutter. 75 | flutter: 76 | # The following line ensures that the Material Icons font is 77 | # included with your application, so that you can use the icons in 78 | # the material Icons class. 79 | uses-material-design: true 80 | #assets: 81 | # - assets/hotel_booking.yaml 82 | #- assets/basic.yaml 83 | #- assets/basic_conditionals.yaml 84 | 85 | # To add assets to your application, add an assets section, like this: 86 | # assets: 87 | # - images/a_dot_burr.jpeg 88 | # - images/a_dot_ham.jpeg 89 | 90 | # An image asset can refer to one or more resolution-specific "variants", see 91 | # https://flutter.dev/assets-and-images/#resolution-aware. 92 | 93 | # For details regarding adding assets from package dependencies, see 94 | # https://flutter.dev/assets-and-images/#from-packages 95 | 96 | # To add custom fonts to your application, add a fonts section here, 97 | # in this "flutter" section. Each entry in this list should have a 98 | # "family" key with the font family name, and a "fonts" key with a 99 | # list giving the asset and other descriptors for the font. For 100 | # example: 101 | # fonts: 102 | # - family: Schyler 103 | # fonts: 104 | # - asset: fonts/Schyler-Regular.ttf 105 | # - asset: fonts/Schyler-Italic.ttf 106 | # style: italic 107 | # - family: Trajan Pro 108 | # fonts: 109 | # - asset: fonts/TrajanPro.ttf 110 | # - asset: fonts/TrajanPro_Bold.ttf 111 | # weight: 700 112 | # 113 | # For details regarding fonts from package dependencies, 114 | # see https://flutter.dev/custom-fonts/#from-packages 115 | -------------------------------------------------------------------------------- /test/primitives_test.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:ensemble_ts_interpreter/invokables/invokableprimitives.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | void main() { 7 | 8 | 9 | test('date parsing', () { 10 | // output is not UTC/GMT 11 | expect(InvokablePrimitive.parseDateTime(1654841398)!.toIso8601String(), '2022-06-09T23:09:58.000'); 12 | expect(InvokablePrimitive.parseDateTime('2022-06-16T19:00:00')!.toIso8601String(), '2022-06-16T19:00:00.000'); 13 | 14 | // output is UTC/GMT 15 | expect(InvokablePrimitive.parseDateTime('2022-06-16T19:00:00.000Z')!.toIso8601String(), '2022-06-16T19:00:00.000Z'); 16 | 17 | expect(InvokablePrimitive.parseDateTime('2022-06-16T12:00:00-0700')!.toIso8601String(), '2022-06-16T19:00:00.000Z'); 18 | expect(InvokablePrimitive.parseDateTime('Thu, 16 Jun 2022 12:00:00 GMT-0700')!.toIso8601String(), '2022-06-16T19:00:00.000Z'); 19 | 20 | expect(InvokablePrimitive.parseDateTime('2022-06-16T12:00:00+0200')!.toIso8601String(), '2022-06-16T10:00:00.000Z'); 21 | expect(InvokablePrimitive.parseDateTime('Thu, 16 Jun 2022 12:00:00 GMT+02:00')!.toIso8601String(), '2022-06-16T10:00:00.000Z'); 22 | }); 23 | 24 | test('date formatter', () { 25 | // date formatted as local date 26 | expect(InvokablePrimitive.prettyDateTime('2022-06-16T12:00:00-0700'), 'Jun 16, 2022 12:00 PM'); 27 | expect(InvokablePrimitive.prettyDate('2022-06-16T12:00:00-0700'), 'Jun 16, 2022'); 28 | }); 29 | 30 | test('duration', () { 31 | // 7,000 secs = 1 hr 56 min 40 se 32 | expect(InvokablePrimitive.prettyDuration(7000), "1 hour 56 minutes"); 33 | expect(InvokablePrimitive.prettyDuration(180654), "2 days 2 hours 10 minutes"); 34 | expect(InvokablePrimitive.prettyDuration(1170654), "1 week 6 days 13 hours 10 minutes"); 35 | }); 36 | } -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:ensemble_ts_interpreter/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test_resources/arrayaccesstest.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"right":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"BinaryExpression","operator":"-","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Identifier","name":"length"},"computed":false,"optional":false},"right":{"type":"Literal","value":1,"raw":"1"}},"computed":true,"optional":false}}},{"type":"ExpressionStatement","expression":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/arraymaptest.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Identifier","name":"map"},"computed":false,"optional":false},"arguments":[{"type":"ArrowFunctionExpression","generator":false,"id":null,"params":[{"type":"Identifier","name":"user"}],"body":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"+=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"user"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false},"right":{"type":"Literal","value":"NEW","raw":"\"NEW\""}}}]},"async":false,"expression":false}],"optional":false}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/es121.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"ConditionalExpression","test":{"type":"BinaryExpression","operator":"==","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"getPrivWiFi"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"status"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"wlanvap"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"vap5g0priv"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"VAPStatus"},"computed":false,"optional":false},"right":{"type":"Literal","value":"Up","raw":"'Up'"}},"consequent":{"type":"Literal","value":true,"raw":"true"},"alternate":{"type":"Literal","value":false,"raw":"false"}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/expression.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensemble"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/getstringvalue.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"stringToBeTranslated"},"init":{"type":"Literal","value":"r@kpn.signin","raw":"'r@kpn.signin'"}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensemble"},"property":{"type":"Identifier","name":"navigateScreen"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":"r@navigateScreen","raw":"'r@navigateScreen'"}],"optional":false}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"response"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false},"right":{"type":"MemberExpression","object":{"type":"Identifier","name":"response"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false}}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"'name'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"r@John","raw":"'r@John'"}}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":1,"raw":"1"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"'name'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"Untranslated Mary","raw":"'Untranslated Mary'"}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/highcharts1.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"a"},"init":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"chart"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"type"},"value":{"type":"Literal","value":"pie","raw":"'pie'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"height"},"value":{"type":"Literal","value":220,"raw":"220"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"width"},"value":{"type":"Literal","value":220,"raw":"220"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"plotOptions"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"pie"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"innerSize"},"value":{"type":"Literal","value":"90%","raw":"'90%'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"borderWidth"},"value":{"type":"Literal","value":4,"raw":"4"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"borderColor"},"value":{"type":"Literal","value":"#FFF","raw":"'#FFF'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"dataLabels"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"enabled"},"value":{"type":"Literal","value":false,"raw":"false"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"credits"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"enabled"},"value":{"type":"Literal","value":false,"raw":"false"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"title"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"text"},"value":{"type":"Literal","value":"TAKE HOME
${getPay.body.earnings.formatted.take_home}","raw":"'TAKE HOME
${getPay.body.earnings.formatted.take_home}'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"floating"},"value":{"type":"Literal","value":true,"raw":"true"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"verticalAlign"},"value":{"type":"Literal","value":"middle","raw":"'middle'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"y"},"value":{"type":"Literal","value":10,"raw":"10"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"tooltip"},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"formatter"},"value":{"type":"FunctionExpression","id":null,"generator":false,"params":[],"body":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"Literal","value":"
","raw":"'
'"},"right":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"ThisExpression"},"property":{"type":"Identifier","name":"point"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false}},"right":{"type":"Literal","value":"

","raw":"'

'"}},"right":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"NewExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"Intl"},"property":{"type":"Identifier","name":"NumberFormat"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":"en-US","raw":"'en-US'"},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"style"},"value":{"type":"Literal","value":"currency","raw":"'currency'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"currency"},"value":{"type":"Literal","value":"USD","raw":"'USD'"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}]},"property":{"type":"Identifier","name":"format"},"computed":false,"optional":false},"arguments":[{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"ThisExpression"},"property":{"type":"Identifier","name":"point"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"y"},"computed":false,"optional":false}],"optional":false}}}]},"async":false,"expression":false},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"series"},"value":{"type":"ArrayExpression","elements":[{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"data"},"value":{"type":"ArrayExpression","elements":[{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"name"},"value":{"type":"Literal","value":"Take home","raw":"'Take home'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"y"},"value":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"getPay"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"earnings"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"take_home"},"computed":false,"optional":false},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"color"},"value":{"type":"Literal","value":"#8AC5A8","raw":"'#8AC5A8'"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"name"},"value":{"type":"Literal","value":"Retirement","raw":"'Retirement'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"y"},"value":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"getPay"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"earnings"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"retirement"},"computed":false,"optional":false},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"color"},"value":{"type":"Literal","value":"#AAD6DE","raw":"'#AAD6DE'"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"name"},"value":{"type":"Literal","value":"Benefits","raw":"'Benefits'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"y"},"value":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"getPay"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"earnings"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"benefits"},"computed":false,"optional":false},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"color"},"value":{"type":"Literal","value":"#FFE7B9","raw":"'#FFE7B9'"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Identifier","name":"name"},"value":{"type":"Literal","value":"Taxes","raw":"'Taxes'"},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"y"},"value":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"getPay"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"earnings"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"taxes"},"computed":false,"optional":false},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Identifier","name":"color"},"value":{"type":"Literal","value":"#DBD4B7","raw":"'#DBD4B7'"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}}],"kind":"var"},{"type":"ReturnStatement","argument":{"type":"Identifier","name":"a"}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/ifstatement.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"IfStatement","test":{"type":"BinaryExpression","operator":"==","left":{"type":"Identifier","name":"age"},"right":{"type":"Literal","value":2,"raw":"2"}},"consequent":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"age","raw":"'age'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"Two years old","raw":"'Two years old'"}}}]},"alternate":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"age","raw":"'age'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"Over Two years old","raw":"'Over Two years old'"}}}]}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/jsonobject.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"body"},"init":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"records","raw":"\"records\""},"value":{"type":"ArrayExpression","elements":[{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recaS1ZTLBhzXnTQE","raw":"\"recaS1ZTLBhzXnTQE\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:15:55.000Z","raw":"\"2022-06-26T19:15:55.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1900","raw":"\"1900\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":3.43,"raw":"3.43"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"rec243aM9qj1rZexl","raw":"\"rec243aM9qj1rZexl\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:15:55.000Z","raw":"\"2022-06-26T19:15:55.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1910","raw":"\"1910\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":4.76,"raw":"4.76"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"receKiv7jDJqHtwNE","raw":"\"receKiv7jDJqHtwNE\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:15:55.000Z","raw":"\"2022-06-26T19:15:55.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1920","raw":"\"1920\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":5.62,"raw":"5.62"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"reccxFr82XvfgY6wk","raw":"\"reccxFr82XvfgY6wk\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1930","raw":"\"1930\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":6.93,"raw":"6.93"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recQJZxryR7kvgKoL","raw":"\"recQJZxryR7kvgKoL\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1940","raw":"\"1940\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.45,"raw":"7.45"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recCAv8xjx3NseYnP","raw":"\"recCAv8xjx3NseYnP\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1950","raw":"\"1950\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.89,"raw":"7.89"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recGIK1eQxZX7bbGW","raw":"\"recGIK1eQxZX7bbGW\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1960","raw":"\"1960\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.78,"raw":"7.78"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recyh6SDypL7htHqz","raw":"\"recyh6SDypL7htHqz\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1970","raw":"\"1970\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.89,"raw":"7.89"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recWd2cx3bXXSF18x","raw":"\"recWd2cx3bXXSF18x\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1980","raw":"\"1980\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.07,"raw":"7.07"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recppFwiLIT29LvF1","raw":"\"recppFwiLIT29LvF1\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"1990","raw":"\"1990\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":7.32,"raw":"7.32"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"receJEkX24mu86qh0","raw":"\"receJEkX24mu86qh0\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"2000","raw":"\"2000\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":8,"raw":"8"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recnemJa8IBpxUqcm","raw":"\"recnemJa8IBpxUqcm\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"2010","raw":"\"2010\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":8.17,"raw":"8.17"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"id","raw":"\"id\""},"value":{"type":"Literal","value":"recke4l6219Jz8hPH","raw":"\"recke4l6219Jz8hPH\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"createdTime","raw":"\"createdTime\""},"value":{"type":"Literal","value":"2022-06-26T19:17:09.000Z","raw":"\"2022-06-26T19:17:09.000Z\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"fields","raw":"\"fields\""},"value":{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"Year","raw":"\"Year\""},"value":{"type":"Literal","value":"2020","raw":"\"2020\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"Population","raw":"\"Population\""},"value":{"type":"Literal","value":8.8,"raw":"8.8"},"computed":false,"method":false,"shorthand":false,"kind":"init"}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}}],"kind":"var"}],"sourceType":"script"} 2 | -------------------------------------------------------------------------------- /test_resources/jsonpath.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "id": "recaS1ZTLBhzXnTQE", 5 | "createdTime": "2022-06-26T19:15:55.000Z", 6 | "fields": { 7 | "Year": "1900", 8 | "Population": 3.43 9 | } 10 | }, 11 | { 12 | "id": "rec243aM9qj1rZexl", 13 | "createdTime": "2022-06-26T19:15:55.000Z", 14 | "fields": { 15 | "Year": "1910", 16 | "Population": 4.76 17 | } 18 | }, 19 | { 20 | "id": "receKiv7jDJqHtwNE", 21 | "createdTime": "2022-06-26T19:15:55.000Z", 22 | "fields": { 23 | "Year": "1920", 24 | "Population": 5.62 25 | } 26 | }, 27 | { 28 | "id": "reccxFr82XvfgY6wk", 29 | "createdTime": "2022-06-26T19:17:09.000Z", 30 | "fields": { 31 | "Year": "1930", 32 | "Population": 6.93 33 | } 34 | }, 35 | { 36 | "id": "recQJZxryR7kvgKoL", 37 | "createdTime": "2022-06-26T19:17:09.000Z", 38 | "fields": { 39 | "Year": "1940", 40 | "Population": 7.45 41 | } 42 | }, 43 | { 44 | "id": "recCAv8xjx3NseYnP", 45 | "createdTime": "2022-06-26T19:17:09.000Z", 46 | "fields": { 47 | "Year": "1950", 48 | "Population": 7.89 49 | } 50 | }, 51 | { 52 | "id": "recGIK1eQxZX7bbGW", 53 | "createdTime": "2022-06-26T19:17:09.000Z", 54 | "fields": { 55 | "Year": "1960", 56 | "Population": 7.78 57 | } 58 | }, 59 | { 60 | "id": "recyh6SDypL7htHqz", 61 | "createdTime": "2022-06-26T19:17:09.000Z", 62 | "fields": { 63 | "Year": "1970", 64 | "Population": 7.89 65 | } 66 | }, 67 | { 68 | "id": "recWd2cx3bXXSF18x", 69 | "createdTime": "2022-06-26T19:17:09.000Z", 70 | "fields": { 71 | "Year": "1980", 72 | "Population": 7.07 73 | } 74 | }, 75 | { 76 | "id": "recppFwiLIT29LvF1", 77 | "createdTime": "2022-06-26T19:17:09.000Z", 78 | "fields": { 79 | "Year": "1990", 80 | "Population": 7.32 81 | } 82 | }, 83 | { 84 | "id": "receJEkX24mu86qh0", 85 | "createdTime": "2022-06-26T19:17:09.000Z", 86 | "fields": { 87 | "Year": "2000", 88 | "Population": 8 89 | } 90 | }, 91 | { 92 | "id": "recnemJa8IBpxUqcm", 93 | "createdTime": "2022-06-26T19:17:09.000Z", 94 | "fields": { 95 | "Year": "2010", 96 | "Population": 8.17 97 | } 98 | }, 99 | { 100 | "id": "recke4l6219Jz8hPH", 101 | "createdTime": "2022-06-26T19:17:09.000Z", 102 | "fields": { 103 | "Year": "2020", 104 | "Population": 8.8 105 | } 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /test_resources/jsonpathints.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"result"},"init":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"response"},"property":{"type":"Identifier","name":"path"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":"$..Year","raw":"'$..Year'"},{"type":"ArrowFunctionExpression","generator":false,"id":null,"params":[{"type":"Identifier","name":"match"}],"body":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"match"},"property":{"type":"Identifier","name":"tryParseInt"},"computed":false,"optional":false},"arguments":[],"optional":false},"async":false,"expression":true}],"optional":false}}],"kind":"var"}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/listsortunique.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"list"},"init":{"type":"ArrayExpression","elements":[{"type":"Literal","value":10,"raw":"10"},{"type":"Literal","value":4,"raw":"4"},{"type":"Literal","value":2,"raw":"2"},{"type":"Literal","value":4,"raw":"4"},{"type":"Literal","value":1,"raw":"1"},{"type":"Literal","value":3,"raw":"3"},{"type":"Literal","value":8,"raw":"8"},{"type":"Literal","value":4,"raw":"4"},{"type":"Literal","value":5,"raw":"5"},{"type":"Literal","value":6,"raw":"6"},{"type":"Literal","value":2,"raw":"2"},{"type":"Literal","value":4,"raw":"4"},{"type":"Literal","value":8,"raw":"8"},{"type":"Literal","value":7,"raw":"7"},{"type":"Literal","value":2,"raw":"2"},{"type":"Literal","value":9,"raw":"9"},{"type":"Literal","value":9,"raw":"9"},{"type":"Literal","value":1,"raw":"1"}]}}],"kind":"var"},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"uniqueList"},"init":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"list"},"property":{"type":"Identifier","name":"unique"},"computed":false,"optional":false},"arguments":[],"optional":false}}],"kind":"var"},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"sortedList"},"init":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"uniqueList"},"property":{"type":"Identifier","name":"sort"},"computed":false,"optional":false},"arguments":[],"optional":false}}],"kind":"var"},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"strList"},"init":{"type":"ArrayExpression","elements":[{"type":"Literal","value":"2","raw":"\"2\""},{"type":"Literal","value":"4","raw":"\"4\""},{"type":"Literal","value":"4","raw":"\"4\""},{"type":"Literal","value":"1","raw":"\"1\""},{"type":"Literal","value":"3","raw":"\"3\""}]}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name":"strList"},"right":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"strList"},"property":{"type":"Identifier","name":"unique"},"computed":false,"optional":false},"arguments":[],"optional":false},"property":{"type":"Identifier","name":"sort"},"computed":false,"optional":false},"arguments":[{"type":"ArrowFunctionExpression","generator":false,"id":null,"params":[{"type":"Identifier","name":"a"},{"type":"Identifier","name":"b"}],"body":{"type":"BlockStatement","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"intA"},"init":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"a"},"property":{"type":"Identifier","name":"tryParseInt"},"computed":false,"optional":false},"arguments":[],"optional":false}}],"kind":"var"},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"intB"},"init":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"b"},"property":{"type":"Identifier","name":"tryParseInt"},"computed":false,"optional":false},"arguments":[],"optional":false}}],"kind":"var"},{"type":"IfStatement","test":{"type":"BinaryExpression","operator":"<","left":{"type":"Identifier","name":"intA"},"right":{"type":"Identifier","name":"intB"}},"consequent":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"UnaryExpression","operator":"-","prefix":true,"argument":{"type":"Literal","value":1,"raw":"1"}}}]},"alternate":{"type":"IfStatement","test":{"type":"BinaryExpression","operator":">","left":{"type":"Identifier","name":"intA"},"right":{"type":"Identifier","name":"intB"}},"consequent":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"Literal","value":1,"raw":"1"}}]},"alternate":null}},{"type":"ReturnStatement","argument":{"type":"Literal","value":0,"raw":"0"}}]},"async":false,"expression":false}],"optional":false}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/maptest.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensembleStore"},"property":{"type":"Identifier","name":"session"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"login"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"contextId"},"computed":false,"optional":false},"right":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"response"},"property":{"type":"Identifier","name":"body"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"data"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"contextID"},"computed":false,"optional":false}}},{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensemble"},"property":{"type":"Identifier","name":"navigateScreen"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":"KPN Home","raw":"\"KPN Home\""}],"optional":false}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/morearrays.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"a"},"init":{"type":"ObjectExpression","properties":[]}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"apiChart"},"property":{"type":"Identifier","name":"data"},"computed":false,"optional":false},"right":{"type":"ArrayExpression","elements":[{"type":"ObjectExpression","properties":[{"type":"Property","key":{"type":"Literal","value":"color","raw":"\"color\""},"value":{"type":"Literal","value":"0xffffcccb","raw":"\"0xffffcccb\""},"computed":false,"method":false,"shorthand":false,"kind":"init"},{"type":"Property","key":{"type":"Literal","value":"data","raw":"\"data\""},"value":{"type":"ArrayExpression","elements":[{"type":"UnaryExpression","operator":"-","prefix":true,"argument":{"type":"Literal","value":97,"raw":"97"}},{"type":"UnaryExpression","operator":"-","prefix":true,"argument":{"type":"Literal","value":33,"raw":"33"}},{"type":"UnaryExpression","operator":"-","prefix":true,"argument":{"type":"Literal","value":57,"raw":"57"}},{"type":"UnaryExpression","operator":"-","prefix":true,"argument":{"type":"Literal","value":56,"raw":"56"}}]},"computed":false,"method":false,"shorthand":false,"kind":"init"}]}]}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/notnulltest.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"IfStatement","test":{"type":"BinaryExpression","operator":"==","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensemble"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false},"right":{"type":"Literal","value":null,"raw":"null"}},"consequent":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"Literal","value":"sad face!","raw":"'sad face!'"}}]},"alternate":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"Literal","value":"it worked!","raw":"'it worked!'"}}]}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/nulltest.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"IfStatement","test":{"type":"BinaryExpression","operator":"==","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensemble"},"property":{"type":"Identifier","name":"test"},"computed":false,"optional":false},"right":{"type":"Literal","value":null,"raw":"null"}},"consequent":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"Literal","value":"it worked!","raw":"'it worked!'"}}]},"alternate":{"type":"BlockStatement","body":[{"type":"ReturnStatement","argument":{"type":"Literal","value":"sad face","raw":"'sad face'"}}]}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/primitives.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"curr"},"init":{"type":"Literal","value":"12.3456","raw":"'12.3456'"}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name":"curr"},"right":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"curr"},"property":{"type":"Identifier","name":"tryParseDouble"},"computed":false,"optional":false},"arguments":[],"optional":false},"property":{"type":"Identifier","name":"prettyCurrency"},"computed":false,"optional":false},"arguments":[],"optional":false}}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"'name'"},"computed":true,"optional":false},"right":{"type":"BinaryExpression","operator":"+","left":{"type":"Literal","value":"John has ","raw":"'John has '"},"right":{"type":"Identifier","name":"curr"}}}}],"sourceType":"script"} 2 | -------------------------------------------------------------------------------- /test_resources/propsthroughquotes.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"ensembleStore"},"property":{"type":"Identifier","name":"session"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"login"},"computed":false,"optional":false},"property":{"type":"Identifier","name":"cookie"},"computed":false,"optional":false},"right":{"type":"MemberExpression","object":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"response"},"property":{"type":"Identifier","name":"headers"},"computed":false,"optional":false},"property":{"type":"Literal","value":"Set-Cookie","raw":"'Set-Cookie'"},"computed":true,"optional":false},"property":{"type":"Identifier","name":"split"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":";","raw":"';'"}],"optional":false},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/returnexpression.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"Literal","value":"quick brown fox ","raw":"'quick brown fox '"},"right":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"\"name\""},"computed":true,"optional":false}},"right":{"type":"Literal","value":" over the fence and received ","raw":"' over the fence and received '"}},"right":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"\"name\""},"computed":true,"optional":false},"property":{"type":"Identifier","name":"length"},"computed":false,"optional":false}},"right":{"type":"Literal","value":" dollars","raw":"' dollars'"}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/returnidentifier.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"Identifier","name":"age"}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/ternary.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"ExpressionStatement","expression":{"type":"ConditionalExpression","test":{"type":"BinaryExpression","operator":">","left":{"type":"Identifier","name":"age"},"right":{"type":"Literal","value":2,"raw":"2"}},"consequent":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"age","raw":"'age'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"More than two years old","raw":"'More than two years old'"}},"alternate":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"age","raw":"'age'"},"computed":true,"optional":false},"right":{"type":"Literal","value":"2 and under","raw":"'2 and under'"}}}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/varArrDecl.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"arr"},"init":{"type":"ArrayExpression","elements":[]}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"arr"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"right":{"type":"Literal","value":"hello","raw":"'hello'"}}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"Identifier","name":"arr"},"property":{"type":"Literal","value":1,"raw":"1"},"computed":true,"optional":false},"right":{"type":"Literal","value":" ","raw":"' '"}}},{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"arr"},"property":{"type":"Identifier","name":"add"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":"nobody","raw":"'nobody'"}],"optional":false}},{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Identifier","name":"map"},"computed":false,"optional":false},"arguments":[{"type":"ArrowFunctionExpression","generator":false,"id":null,"params":[{"type":"Identifier","name":"user"}],"body":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"arr"},"property":{"type":"Identifier","name":"add"},"computed":false,"optional":false},"arguments":[{"type":"Literal","value":" ","raw":"' '"}],"optional":false}},{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"arr"},"property":{"type":"Identifier","name":"add"},"computed":false,"optional":false},"arguments":[{"type":"BinaryExpression","operator":"+","left":{"type":"Literal","value":"hello ","raw":"'hello '"},"right":{"type":"MemberExpression","object":{"type":"Identifier","name":"user"},"property":{"type":"Identifier","name":"name"},"computed":false,"optional":false}}],"optional":false}}]},"async":false,"expression":false}],"optional":false}}],"sourceType":"script"} -------------------------------------------------------------------------------- /test_resources/variabledecl.json: -------------------------------------------------------------------------------- 1 | {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"user"},"init":{"type":"Literal","value":"John Doe","raw":"'John Doe'"}}],"kind":"let"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"+=","left":{"type":"Identifier","name":"user"},"right":{"type":"Literal","value":" II","raw":"' II'"}}},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"age"},"init":null}],"kind":"let"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name":"age"},"right":{"type":"Literal","value":12,"raw":"12"}}},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"+=","left":{"type":"Identifier","name":"age"},"right":{"type":"Literal","value":3,"raw":"3"}}},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"curr"},"init":{"type":"Literal","value":12.9382929,"raw":"12.9382929"}}],"kind":"var"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name":"curr"},"right":{"type":"CallExpression","callee":{"type":"MemberExpression","object":{"type":"Identifier","name":"curr"},"property":{"type":"Identifier","name":"prettyCurrency"},"computed":false,"optional":false},"arguments":[],"optional":false}}},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"str"},"init":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"BinaryExpression","operator":"+","left":{"type":"Literal","value":"user=","raw":"'user='"},"right":{"type":"Identifier","name":"user"}},"right":{"type":"Literal","value":" is ","raw":"' is '"}},"right":{"type":"Identifier","name":"age"}},"right":{"type":"Literal","value":" years old and has ","raw":"' years old and has '"}},"right":{"type":"Identifier","name":"curr"}}}],"kind":"let"},{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"MemberExpression","object":{"type":"MemberExpression","object":{"type":"Identifier","name":"users"},"property":{"type":"Literal","value":0,"raw":"0"},"computed":true,"optional":false},"property":{"type":"Literal","value":"name","raw":"'name'"},"computed":true,"optional":false},"right":{"type":"Identifier","name":"str"}}}],"sourceType":"script"} --------------------------------------------------------------------------------