@datagrok/eda 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +3 -0
  2. package/detectors.js +9 -0
  3. package/dist/111.js +2 -0
  4. package/dist/146.js +2 -0
  5. package/dist/155.js +2 -0
  6. package/dist/355.js +2 -0
  7. package/dist/584.js +2 -0
  8. package/dist/604.js +2 -0
  9. package/dist/632.js +2 -0
  10. package/dist/645.js +2 -0
  11. package/dist/93.js +2 -0
  12. package/dist/d711f70338306e5bddc4.wasm +0 -0
  13. package/dist/package-test.js +2 -0
  14. package/dist/package.js +2 -0
  15. package/package.json +49 -0
  16. package/package.png +0 -0
  17. package/scripts/command.txt +1 -0
  18. package/scripts/exportForTS.py +862 -0
  19. package/scripts/exportForTSConstants.py +93 -0
  20. package/scripts/func.json +1 -0
  21. package/scripts/module.json +11 -0
  22. package/src/EDAtools.ts +46 -0
  23. package/src/EDAui.ts +118 -0
  24. package/src/dataGenerators.ts +74 -0
  25. package/src/demos.ts +38 -0
  26. package/src/package-test.ts +12 -0
  27. package/src/package.ts +248 -0
  28. package/src/svm.ts +485 -0
  29. package/src/utils.ts +51 -0
  30. package/tsconfig.json +71 -0
  31. package/wasm/EDA.js +443 -0
  32. package/wasm/EDA.wasm +0 -0
  33. package/wasm/EDAAPI.js +131 -0
  34. package/wasm/EDAForWebWorker.js +21 -0
  35. package/wasm/PCA/PCA.cpp +151 -0
  36. package/wasm/PCA/PCA.h +48 -0
  37. package/wasm/PLS/PLS.h +64 -0
  38. package/wasm/PLS/pls.cpp +393 -0
  39. package/wasm/callWasm.js +475 -0
  40. package/wasm/callWasmForWebWorker.js +706 -0
  41. package/wasm/dataGenerators.h +169 -0
  42. package/wasm/dataMining.h +116 -0
  43. package/wasm/pcaExport.cpp +64 -0
  44. package/wasm/plsExport.cpp +75 -0
  45. package/wasm/svm.h +608 -0
  46. package/wasm/svmApi.cpp +323 -0
  47. package/wasm/workers/errorWorker.js +13 -0
  48. package/wasm/workers/generateDatasetWorker.js +13 -0
  49. package/wasm/workers/normalizeDatasetWorker.js +13 -0
  50. package/wasm/workers/partialLeastSquareRegressionWorker.js +13 -0
  51. package/wasm/workers/predictByLSSVMWorker.js +13 -0
  52. package/wasm/workers/principalComponentAnalysisWorker.js +13 -0
  53. package/wasm/workers/trainAndAnalyzeLSSVMWorker.js +13 -0
  54. package/wasm/workers/trainLSSVMWorker.js +13 -0
  55. package/webpack.config.js +37 -0
@@ -0,0 +1,862 @@
1
+ """ export.py March 02, 2023.
2
+ This script exports C/C++-functions to Datagrok package.
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import shutil
8
+
9
+ from exportForTSConstants import *
10
+
11
+
12
+ def getSettings(nameOfFile):
13
+ """
14
+ Return dictionary with settings of C/C++-functions export from json-file with settings.
15
+ """
16
+ with open(nameOfFile, READ_MODE) as file:
17
+ return json.load(file)
18
+
19
+ class Function:
20
+ """ Exported C/C++-function specification class.
21
+ """
22
+ def __init__(self, cFuncAnnotationLines, typeList):
23
+ self.annotation = []
24
+ self.arguments = {}
25
+ self.prototype = ''
26
+ self.prototypeForWebWorker = ''
27
+ self.callArgs = '['
28
+ self.typeList = typeList
29
+ self.cFuncTypeIndex = 0
30
+ self.output = {}
31
+ self.paramsWithNewSpecifier = {}
32
+
33
+ # process each line of annotation
34
+ for line in cFuncAnnotationLines:
35
+ self.processAnnotationLine(line)
36
+
37
+ # complete output of specification
38
+ self.finalizeOutput()
39
+
40
+ # final preparation of prototypes
41
+ self.prototype = self.prototype.rstrip(', ')
42
+ self.prototype += ')'
43
+ self.prototypeForWebWorker = self.prototypeForWebWorker.rstrip(', ')
44
+ self.prototypeForWebWorker += ')'
45
+
46
+ # final preparation of call args string
47
+ self.callArgs = self.callArgs.rstrip(', ')
48
+ self.callArgs += ']'
49
+
50
+ # create specification dict
51
+ self.specification = {ARGUMENTS: self.arguments,
52
+ OUTPUT: self.output,
53
+ ANNOTATION: self.annotation,
54
+ PROTOTYPE: self.prototype,
55
+ WW_PROTOTYPE: self.prototypeForWebWorker,
56
+ CALL_ARGS: self.callArgs}
57
+
58
+ def processAnnotationLine(self, line):
59
+ """ Process one line of annotation before exported C/C++-function.
60
+ """
61
+ if line.startswith(ANNOT_INPUT):
62
+ self.processInputLine(line)
63
+ elif line.startswith(ANNOT_OUTPUT):
64
+ self.processOutputLine(line)
65
+ elif line.startswith(ANNOT_NAME):
66
+ self.processNameLine(line)
67
+ else:
68
+ self.annotation.append(line)
69
+
70
+ def processNameLine(self, line):
71
+ """ Process line containing '//name:'.
72
+ """
73
+ ignore1, ignore2, name = line.partition(' ')
74
+ self.prototype += f'{name}('
75
+ self.prototypeForWebWorker += f'{name}{IN_WEBWORKER_SUFFIX}('
76
+ self.annotation.append(line)
77
+
78
+ def processOutputLine(self, line):
79
+ """ Process line containing '//output:'.
80
+ """
81
+ if ANNOT_NEW in line:
82
+ self.processOutputLineWithNew(line)
83
+ else:
84
+ self.processOutputLineWithoutNew(line)
85
+
86
+ def processInputLine(self, line):
87
+ """ Process '//input'-line with arguments that are passed.
88
+ """
89
+ self.annotation.append(line)
90
+
91
+ # get type and name part of line
92
+ ignore1, ignore2, rest = line.partition(': ')
93
+
94
+ # get type and name of the argument
95
+ argType, ignore, argName = rest.partition(' ')
96
+
97
+ self.prototype += argName + ', '
98
+ self.prototypeForWebWorker += argName + ', '
99
+
100
+ if argType == ANNOT_DATAFRAME:
101
+ return
102
+
103
+ self.callArgs += argName + ', '
104
+
105
+ currentType = self.typeList[self.cFuncTypeIndex]
106
+
107
+ if argType == ANNOT_COLUMN:
108
+ self.arguments[argName] = {TYPE: currentType + COLUMN}
109
+ self.cFuncTypeIndex += 2
110
+
111
+ elif argType == ANNOT_COLUMN_LIST:
112
+ self.arguments[argName] = {TYPE: currentType + COLUMNS}
113
+ self.cFuncTypeIndex += 3
114
+
115
+ else:
116
+ self.arguments[argName] = {TYPE: NUM}
117
+ self.cFuncTypeIndex += 1
118
+
119
+ def processOutputLineWithNew(self, line):
120
+ """ Process '//output'-line with arguments that are newly created.
121
+ """
122
+ # get type and name part of line
123
+ ignore1, ignore2, rest = line.partition(': ')
124
+
125
+ # get type and name of the argument
126
+ argType, ignore, rest = rest.partition(' ')
127
+
128
+ if argType == ANNOT_DATAFRAME:
129
+ return
130
+
131
+ # extract name & specification
132
+ argName, ignore, specification = rest.partition(' ')
133
+
134
+ # store argument info in dictionary
135
+ self.paramsWithNewSpecifier[argName] = argType
136
+
137
+ # extract the main data
138
+ specification = specification.lstrip(f'[{ANNOT_NEW}(').rstrip(')]')
139
+
140
+ currentType = self.typeList[self.cFuncTypeIndex]
141
+
142
+ if argType == ANNOT_COLUMN:
143
+ specification = specification.strip()
144
+
145
+ ref = None
146
+ value = None
147
+
148
+ if ANNOT_DOT not in specification:
149
+ ref = specification
150
+ value = DATA
151
+ else:
152
+ ref, ignore, value = specification.partition(ANNOT_DOT)
153
+
154
+ self.arguments[argName] = {TYPE: NEW + currentType.capitalize() + COLUMN}
155
+ self.arguments[argName][NUM_OF_ROWS] = {REF: ref, VALUE: sizesMap[value]}
156
+ self.cFuncTypeIndex += 2
157
+
158
+ elif argType == ANNOT_COLUMN_LIST:
159
+
160
+ first, ignore, last = specification.partition(',')
161
+ first = first.strip()
162
+ last = last.strip()
163
+
164
+ self.arguments[argName] = {TYPE: NEW + currentType.capitalize() + COLUMNS}
165
+
166
+ ref = None
167
+ value = None
168
+
169
+ if ANNOT_DOT not in first:
170
+ ref = first
171
+ value = DATA
172
+ else:
173
+ ref, ignore, value = first.partition(ANNOT_DOT)
174
+
175
+ self.arguments[argName][NUM_OF_ROWS] = {REF: ref, VALUE: sizesMap[value]}
176
+
177
+ if ANNOT_DOT not in last:
178
+ ref = last
179
+ value = DATA
180
+ else:
181
+ ref, ignore, value = last.partition(ANNOT_DOT)
182
+
183
+ self.arguments[argName][NUM_OF_COLUMNS] = {REF: ref, VALUE: sizesMap[value]}
184
+
185
+ self.cFuncTypeIndex += 3
186
+
187
+ else:
188
+ self.arguments[argName] = {TYPE: NUM}
189
+ self.cFuncTypeIndex += 1
190
+
191
+ def processOutputLineWithoutNew(self, line):
192
+ """ Process '//output'-line.
193
+ """
194
+ ignore1, ignore2, specification = line.partition(':')
195
+
196
+ outputType, ignore, outputSource = specification.strip().partition(' ')
197
+
198
+ outputType = outputType.strip()
199
+
200
+ ignore1, ignore2, outputSource = outputSource.partition(' ')
201
+
202
+ outputSource = outputSource.strip().strip('[]')
203
+
204
+ if outputType == ANNOT_OBJECTS:
205
+ self.output[TYPE] = outputType
206
+ args = outputSource.split(',')
207
+ sourceString = ''
208
+
209
+ for arg in args:
210
+ sourceString += f"'{arg.strip()}', "
211
+
212
+ self.output[SOURCE] = f"[{sourceString.rstrip(', ')}]"
213
+
214
+ return
215
+
216
+ elif outputType == ANNOT_DATAFRAME:
217
+ self.output[TYPE] = TABLE_FROM_COLUMNS
218
+ self.output[SOURCE] = f'{outputSource}'
219
+
220
+ else:
221
+ self.output[TYPE] = f'{outputType}'
222
+ self.output[SOURCE] = f'{outputSource}'
223
+
224
+ stringToAnnotation, ignore1, ignore2 = line.partition('[')
225
+ self.annotation.append(stringToAnnotation)
226
+
227
+ def finalizeOutput(self):
228
+ """ Complete output part of the function specification.
229
+ """
230
+ if len(self.output) == 0:
231
+ if len(self.paramsWithNewSpecifier) == 0:
232
+ raise Warning
233
+ elif len(self.paramsWithNewSpecifier) == 1:
234
+ argName = list(self.paramsWithNewSpecifier.keys())[0]
235
+ self.output[TYPE] = self.paramsWithNewSpecifier[argName]
236
+ self.output[SOURCE] = argName
237
+ self.annotation.append(f'{ANNOT_OUTPUT} {self.output[TYPE]} {argName}')
238
+ else:
239
+ self.output[TYPE] = OBJECTS
240
+ sourceString = '['
241
+
242
+ for name in self.paramsWithNewSpecifier.keys():
243
+ sourceString += f"'{name}'" + ', '
244
+
245
+ sourceString = sourceString.rstrip(', ')
246
+ sourceString += ']'
247
+ self.output[SOURCE] = sourceString
248
+
249
+ def getSpecification(self):
250
+ return self.specification
251
+
252
+ def getFunctionsFromFile(nameOfFile):
253
+ """
254
+ Parse C/C++-file and return specification of exported functions.
255
+ """
256
+ with open(nameOfFile, READ_MODE) as file:
257
+ listOfLines = file.readlines()
258
+ dictionaryOfFunctions = {}
259
+
260
+ for lineIndex in range(len(listOfLines)):
261
+ # the next line is function's prototype
262
+ if listOfLines[lineIndex].startswith(EM_MACROS):
263
+
264
+ # 1. Analyze function description
265
+ j = lineIndex + 1
266
+ funcLine = ''
267
+
268
+ # 1.1) get one line prototype of exported function
269
+ while True:
270
+ funcLine += listOfLines[j].strip().rstrip('\n')
271
+ if ')' in listOfLines[j]:
272
+ break
273
+ j += 1
274
+
275
+ # 1.2) get name of function & a string of its arguments
276
+ ignore1, ignore2, nameAndArgs = funcLine.partition(' ')
277
+ functionName, ignore, argsLine = nameAndArgs.partition('(')
278
+
279
+ # 1.3) get types of arguments
280
+ cFuncArgs = argsLine.split(',')
281
+ typeList = [] # list of argument's type
282
+
283
+ for s in cFuncArgs:
284
+ argType, ignore1, ignore2 = s.strip().partition(' ')
285
+ typeList.append(argType)
286
+
287
+ # 1.4) get annotation lines
288
+ j = lineIndex - 1
289
+ annotationLines = []
290
+
291
+ while listOfLines[j].startswith('//'):
292
+ annotationLines.append(listOfLines[j].strip())
293
+ j -= 1
294
+
295
+ annotationLines.reverse()
296
+ function = Function(annotationLines, typeList)
297
+ dictionaryOfFunctions[functionName] = function.getSpecification()
298
+
299
+ return dictionaryOfFunctions
300
+
301
+ def getFunctionsFromListOfFiles(settings):
302
+ """ Loads a list of C-file names and gets functions from each of these files
303
+ """
304
+ functions = {} # dictionary: key - name of file; value - functions data
305
+
306
+ for name in settings[SOURCE]:
307
+ functions[name] = getFunctionsFromFile(settings[FOLDER] + '/' + name)
308
+
309
+ return functions
310
+
311
+ def saveFunctionsDataToFile(functionsData, nameOfFile):
312
+ """
313
+ Save C-functions descriptors to json-file.
314
+ """
315
+ with open(nameOfFile, WRITE_MODE) as file:
316
+ json.dump(functionsData, file)
317
+
318
+ def getCommand(settings, functionsData):
319
+ """
320
+ Return Emscripten command based on settings and functions data.
321
+ """
322
+ command = 'em++' # Emscripten command, starts with "emcc"
323
+
324
+ # set optimization mode
325
+ command += ' ' + settings[OPTIMIZATION_MODE]
326
+
327
+ # add names of C-files
328
+ for nameOfFile in settings[SOURCE]:
329
+ command += f' {settings[FOLDER]}/{nameOfFile}'
330
+
331
+ # add name of JS-library that is created by Emscripten
332
+ command += f' -o {settings[FOLDER]}/{settings[NAME]}.js'
333
+
334
+ # specify memory restrictions
335
+ command += ' -s TOTAL_MEMORY=' + settings[TOTAL_MEMORY]
336
+
337
+ # set that wasm-file must be created
338
+ command += ' -s WASM=1'
339
+
340
+ # allow memory growth
341
+ command += ' -s ALLOW_MEMORY_GROWTH=1'
342
+
343
+ # create module (? should be checked!)
344
+ command += ' -s MODULARIZE=1'
345
+
346
+ # set export name
347
+ command += ' -s EXPORT_NAME="export' + settings[NAME] + '"'
348
+
349
+ # set a list of exported functions
350
+ command += ' -s EXPORTED_FUNCTIONS=['
351
+
352
+ # add names of functions
353
+ for nameOfFile in functionsData.keys():
354
+ for nameOfFunction in functionsData[nameOfFile].keys():
355
+ command += '"_' + nameOfFunction + '",'
356
+
357
+ # add malloc and free - they are used for memory operating
358
+ command += '"_malloc","_free"]'
359
+
360
+ # add exported runtime methods
361
+ command += ' -s EXPORTED_RUNTIME_METHODS=["cwrap","ccall"]' # also, "ccall" can be added
362
+
363
+ # the following is required when running in webworker
364
+ command += ' -sENVIRONMENT=web,worker'
365
+
366
+ return command
367
+
368
+ def saveCommand(command, nameOfFile):
369
+ """
370
+ Save Emscripten command to txt-file.
371
+ """
372
+ with open(nameOfFile, WRITE_MODE) as file:
373
+ file.write(command)
374
+
375
+ def createJSwrapperForWasmInWebWorkerRun(settings):
376
+ """
377
+ Create JS-wrapper for wasm-functions run in webworkers.
378
+ """
379
+ folder = settings[FOLDER]
380
+ name = settings[NAME]
381
+
382
+ # open JS-file generated by Emscripten
383
+ with open(f'{folder}/{name}{EM_LIB_EXTENSION}', READ_MODE) as emFile:
384
+ listOfLines = emFile.readlines()
385
+
386
+ # this modification provides further usage of the module in webworker
387
+ listOfLines[NUM_OF_LINE_TO_MODIFY] = KEY_WORD_TO_ADD + listOfLines[NUM_OF_LINE_TO_MODIFY]
388
+
389
+ # create JS-wrapper for call wasm-functions in webworker
390
+ with open(f'{folder}/{name}{WW_FILE_SUFFIX}{EM_LIB_EXTENSION}',WRITE_MODE) as file:
391
+
392
+ replacement = f"fetch(new URL('{folder}/{name}.wasm', import.meta.url))"
393
+
394
+ # put lines (replacement is required for further usage in webworkers)
395
+ for line in listOfLines:
396
+ file.write(line.replace(LINE_TO_REPLACE, replacement))
397
+
398
+ def createWorkerFiles(settings, functionsData):
399
+ """
400
+ Create worker files corresponding to each exported function.
401
+ """
402
+ folder = settings[FOLDER]
403
+ name = settings[NAME]
404
+ runtimeSystem = settings[RUNTIME_SYSTEM_FOR_WEBWORKER]
405
+
406
+ folderName = f'{folder}/{WORKERS_FOLDER}'
407
+
408
+ # check existnace of folder for workers
409
+ if not os.path.exists(folderName):
410
+ os.mkdir(folderName)
411
+
412
+ # generation of workers for each file and each exported function
413
+ for nameOfFile in functionsData.keys():
414
+ for funcName in functionsData[nameOfFile].keys():
415
+
416
+ # create name of worker
417
+ workerName = f'{folderName}/{funcName}{WORKER_SUFFIX}{WORKER_EXTENSION}'
418
+
419
+ # create file and generate code of the current worker
420
+ with open(workerName, WRITE_MODE) as file:
421
+ put = file.write
422
+ put(f'{AUTOMATIC_GENERATION_LINE}\n\n')
423
+ libFile = f'../{folder}/{name}{WW_FILE_SUFFIX}'
424
+ put('import {export' + name + '}' + f" from '{libFile}';\n")
425
+ put('import {' + CPP_WRAPPER_FUNCTION + '}')
426
+ put(f" from '../{runtimeSystem}';\n\n")
427
+ put('onmessage = async function (evt) {\n')
428
+ put(f'{WW_SPACE}export{name}().then(module => \n')
429
+ put(WW_SUBSPACE + '{\n')
430
+ put(f'{WW_SUBSUBSPACE}let args = evt.data;\n')
431
+ line = f"let result = cppWrapper(module, args, '{funcName}', 'number');"
432
+ put(f'{WW_SUBSUBSPACE}{line}\n')
433
+ put(WW_SUBSUBSPACE + "postMessage({'callResult': result, 'args': args});\n")
434
+ put(WW_SUBSPACE + '} )\n}')
435
+
436
+ def updatePackageJsonFile(settings):
437
+ """ Add JS-file with exported C/C++-functions to "sources" of package.json.
438
+ """
439
+ # data from package.json
440
+ packageData = None
441
+
442
+ # get package data from package.json
443
+ with open(settings[PACKAGE_JSON_FILE], READ_MODE) as file:
444
+ packageData = json.load(file)
445
+
446
+ # create full name of JS-lib file that is added to package.json
447
+ fullNameOfLibFile = f"{settings[FOLDER].lstrip('../')}/{settings[NAME]}.js"
448
+
449
+ # add dependence to package data
450
+ if PJSN_SOURCES in packageData.keys():
451
+ # add JS-file if "source" does not contain it yet
452
+ if fullNameOfLibFile not in packageData[PJSN_SOURCES]:
453
+ packageData[PJSN_SOURCES].append(fullNameOfLibFile)
454
+ else:
455
+ packageData[PJSN_SOURCES] = [ fullNameOfLibFile ]
456
+
457
+ # update package.json
458
+ with open(settings[PACKAGE_JSON_FILE], WRITE_MODE) as file:
459
+ json.dump(packageData, file)
460
+
461
+ def completeJsWasmfile(settings, functionsData):
462
+ """
463
+ Add exported C/C++-function specifications and the corresponding init-function
464
+ to JS-file created by Emscripten.
465
+ """
466
+ with open(f'{settings[FOLDER]}/{settings[NAME]}.js', APPEND_MODE) as file:
467
+ put = file.write
468
+ put('\n\n')
469
+
470
+ exportedFuncsNames = []
471
+
472
+ for cppFile in functionsData.keys():
473
+ for funcName in functionsData[cppFile].keys():
474
+ exportedFuncsNames.append(funcName)
475
+ put(f'var {funcName} = ' + '{\n')
476
+ arguments = functionsData[cppFile][funcName][ARGUMENTS]
477
+ put(f'{SPACE}{ARGUMENTS}: ' + '{\n')
478
+ argCommasCount = len(arguments.keys()) - 1
479
+
480
+ for arg in arguments.keys():
481
+ put(f'{SUBSPACE}{arg}: ' + '{\n')
482
+ attrCommasCount = len(arguments[arg].keys()) - 1
483
+
484
+ for attribute in arguments[arg].keys():
485
+
486
+ if attribute == TYPE:
487
+ put(f"{SUBSUBSPACE}{attribute}: '{arguments[arg][attribute]}'")
488
+
489
+ if attrCommasCount > 0:
490
+ put(',')
491
+ attrCommasCount -= 1
492
+
493
+ put('\n')
494
+
495
+ else:
496
+ put(f'{SUBSUBSPACE}{attribute}: ' + '{\n')
497
+ ref = arguments[arg][attribute][REF]
498
+ put(f"{SUBSUBSUBSPACE}{REF}: '{ref}',\n")
499
+ value = arguments[arg][attribute][VALUE]
500
+ put(f"{SUBSUBSUBSPACE}{VALUE}: '{value}'\n")
501
+ put(SUBSUBSPACE + '}')
502
+
503
+ if attrCommasCount > 0:
504
+ put(',')
505
+ attrCommasCount -= 1
506
+
507
+ put('\n')
508
+
509
+ put(SUBSPACE + '}')
510
+
511
+ if argCommasCount > 0:
512
+ put(',')
513
+ argCommasCount -= 1
514
+
515
+ put('\n')
516
+
517
+ put(SPACE + '},\n')
518
+ put(f'{SPACE}{OUTPUT}: ' + '{\n')
519
+ outputType = functionsData[cppFile][funcName][OUTPUT][TYPE]
520
+ put(f"{SUBSPACE}{TYPE}: '{outputType}',\n")
521
+ outputSource = functionsData[cppFile][funcName][OUTPUT][SOURCE]
522
+ put(f'{SUBSPACE}{SOURCE}: ')
523
+
524
+ if outputType != OBJECTS:
525
+ put(f"'{outputSource}'")
526
+ else:
527
+ put(f'{outputSource}')
528
+
529
+ put(f'\n{SPACE}' + '}\n')
530
+ put('}; ' + f'// {funcName}\n\n')
531
+
532
+ # add init-function
533
+ put(f'var {settings[NAME]} = undefined;\n\n')
534
+ put(f'async function init{settings[NAME]}() ' + '{\n')
535
+ put(f'{SPACE}if ({settings[NAME]} === undefined) ' + '{\n')
536
+ put(f'{SUBSPACE}console.log("Wasm not Loaded, Loading");\n')
537
+ put(f'{SUBSPACE}{settings[NAME]} = await export{settings[NAME]}();\n')
538
+
539
+ for name in exportedFuncsNames:
540
+ put(f'{SUBSPACE}{settings[NAME]}.{name} = {name};\n')
541
+
542
+ put(SPACE + '} else {\n')
543
+ put(f'{SUBSPACE}console.log("Wasm Loaded, Passing");\n')
544
+ put(SPACE + '}\n')
545
+ put('}')
546
+
547
+ def generateApiFile(settings, functionsData):
548
+ """ Generate code in the package-file.
549
+ """
550
+ def generateFuncsForMainStreamRun(put, funcName, funcData):
551
+ """
552
+ Generate code of functions for run in the main stream.
553
+ """
554
+
555
+ # put package function code
556
+ put(f'export function {SERVICE_PREFFIX}{funcData[PROTOTYPE]} ' + '{\n')
557
+ put(f"{SPACE}return callWasm({settings[NAME]}, '{funcName}', {funcData[CALL_ARGS]});\n")
558
+ put('}\n\n')
559
+
560
+ def generateFuncsForWebWorker(put, funcName, funcData):
561
+ """
562
+ Generate code of functions for run in webworker.
563
+ """
564
+
565
+ # put package function code
566
+ put(f'export async function {SERVICE_PREFFIX}{funcData[WW_PROTOTYPE]} ' + '{\n')
567
+
568
+ workerName = f'{settings[FOLDER]}/{WORKERS_FOLDER}/{funcName}{WORKER_SUFFIX}{WORKER_EXTENSION}'
569
+
570
+ put(f'{SPACE}return new Promise((resolve, reject) => ')
571
+ put('{\n')
572
+ put(f"{SUBSPACE}const worker = new Worker(new URL('{workerName}', import.meta.url));\n")
573
+ put(f"{SUBSPACE}worker.postMessage(getCppInput({settings[NAME]}['{funcName}'].arguments,")
574
+ put(f"{funcData[CALL_ARGS]}));\n{SUBSPACE}")
575
+ put('worker.onmessage = function(e) {\n')
576
+ put(f'{SUBSUBSPACE}worker.terminate();\n')
577
+ put(f'{SUBSUBSPACE}resolve(')
578
+ put(f"getResult({settings[NAME]}['{funcName}'], e.data));\n")
579
+ put(SUBSPACE)
580
+ put('}\n')
581
+ put(SPACE)
582
+ put('});\n')
583
+ put('}\n\n')
584
+
585
+ apiFile = f'../wasm/{settings[NAME]}{API_SUFFIX}.js'
586
+
587
+ with open(apiFile, WRITE_MODE) as file:
588
+ put = file.write
589
+
590
+ put(AUTOMATIC_GENERATION_LINE)
591
+ put(f'\n// JavaScript API for call wasm-functions from the module {settings[NAME]}\n')
592
+ put('\n// Imports for call wasm runtime-system: in the main stream and in webworkers\n')
593
+
594
+ # put import-line: wasm-computations in the main stream
595
+ put('import {' + CALL_WASM + '} from ')
596
+ callWasm = settings[RUNTIME_SYSTEM].rstrip('.js')
597
+ put(f"'{callWasm}';\n")
598
+
599
+ # put import-line: wasm-computations in webworkers
600
+ put('import {' + f'{GET_CPP_INPUT}, {GET_RESULT}' + '} from ')
601
+ callWasmWW = settings[RUNTIME_SYSTEM_FOR_WEBWORKER].rstrip('.js')
602
+ put(f"'{callWasmWW}';\n\n")
603
+
604
+ # put init-function
605
+ #put('//tags: init\n')
606
+ put(f'export async function _init{settings[NAME]}API() ' + '{\n')
607
+ put(f'{SPACE}await init{settings[NAME]}();\n')
608
+ put('}\n\n')
609
+
610
+ # put call of each exported C/C++-function
611
+ for cppFileName in functionsData.keys():
612
+ for funcName in functionsData[cppFileName].keys():
613
+ generateFuncsForMainStreamRun(put, funcName, functionsData[cppFileName][funcName])
614
+ generateFuncsForWebWorker(put, funcName, functionsData[cppFileName][funcName])
615
+
616
+ def generateCodeInPackageTsFile(settings, functionsData):
617
+ """
618
+ Generate code of wasm-computations call in the file package.ts.
619
+ """
620
+ def putImportLines(put, settings, functionsData):
621
+ """
622
+ Generate import lines.
623
+ """
624
+ put('\nimport {')
625
+
626
+ # init module function
627
+ put(f'_init{settings[NAME]}{API_SUFFIX}')
628
+
629
+ # put other import functions
630
+ for cppFileName in functionsData.keys():
631
+ for funcName in functionsData[cppFileName].keys():
632
+ put(f', {SERVICE_PREFFIX}{funcName},')
633
+ put(f' {SERVICE_PREFFIX}{funcName}{IN_WEBWORKER_SUFFIX}')
634
+
635
+ put('}')
636
+ put(f" from '../wasm/{settings[NAME]}{API_SUFFIX}';\n\n")
637
+
638
+ def putInitFunction(put, settings):
639
+ """
640
+ Generate code of init-function.
641
+ """
642
+ put('//tags: init\n')
643
+ put('export async function init(): Promise<void> {\n')
644
+ put(f'{SPACE}await _init{settings[NAME]}{API_SUFFIX}();\n')
645
+ put('}\n\n')
646
+
647
+ def getTStype(argType):
648
+ """
649
+ Returns TypeScript type.
650
+ """
651
+ if argType in typesMap.keys():
652
+ return typesMap[argType]
653
+ return ANY_TYPE
654
+
655
+ def getArgTypeAndArgName(annotationLine):
656
+ """
657
+ Returns a tuple: (argType,argName)
658
+ """
659
+ ignore1, ignore2, rest = annotationLine.partition(' ')
660
+ rest.strip(' ')
661
+ argType, ignore1, rest = rest.partition(' ')
662
+ rest.strip(' ')
663
+ argName, ignore1, ignore2 = rest.partition(' ')
664
+ return argType, argName
665
+
666
+ def putPrototype(put, funcName, funcData):
667
+ """
668
+ Put prototype of the function for the main thread call.
669
+ """
670
+ put(f'export function {funcName}(')
671
+
672
+ # default return type
673
+ returnType = 'void'
674
+
675
+ arguments = ''
676
+
677
+ # put arguments
678
+ for line in funcData[ANNOTATION]:
679
+ if line.startswith(ANNOT_INPUT):
680
+ argType, argName = getArgTypeAndArgName(line)
681
+ arguments += f'{argName}: {getTStype(argType)}, '
682
+ elif line.startswith(ANNOT_OUTPUT):
683
+ returnType, argName = getArgTypeAndArgName(line)
684
+
685
+ arguments.rstrip(', ')
686
+ put(arguments.rstrip(', '))
687
+
688
+ put(f'): {getTStype(returnType)}')
689
+
690
+ put(' {\n')
691
+
692
+ def putPrototypeForWebWorker(put, funcName, funcData):
693
+ """
694
+ Put prototype of the function for call in webworker.
695
+ """
696
+ put(f'export async function {funcName}{IN_WEBWORKER_SUFFIX}(')
697
+
698
+ # default return type
699
+ returnType = 'void'
700
+
701
+ arguments = ''
702
+
703
+ # put arguments
704
+ for line in funcData[ANNOTATION]:
705
+ if line.startswith(ANNOT_INPUT):
706
+ argType, argName = getArgTypeAndArgName(line)
707
+ arguments += f'{argName}: {getTStype(argType)}, '
708
+ elif line.startswith(ANNOT_OUTPUT):
709
+ returnType, argName = getArgTypeAndArgName(line)
710
+
711
+ arguments.rstrip(', ')
712
+ put(arguments.rstrip(', '))
713
+
714
+ put(f'): Promise<{getTStype(returnType)}>')
715
+
716
+ put(' {\n')
717
+
718
+ def generateWasmCall(put, funcName, funcData):
719
+ """
720
+ Generate code for wasm function call in the main thread.
721
+ """
722
+ # put annotation lines
723
+ for line in funcData[ANNOTATION]:
724
+ put(f'{line}\n')
725
+
726
+ # put function prototype
727
+ putPrototype(put, funcName, funcData)
728
+
729
+ # put main body
730
+ put(f'{SPACE}return {SERVICE_PREFFIX}{funcData[PROTOTYPE]};\n')
731
+
732
+ put('}\n\n')
733
+
734
+ def putMainBodyForWebWorker(put, funcName, funcData):
735
+ """
736
+ Put main body for call wasm-computations in webworker.
737
+ """
738
+ put(f'{SPACE}let {OUTPUT_VARIABLE}: any;\n')
739
+ put(f'{SPACE}let {PROMISE_VARIABLE} = ')
740
+ put(f'{SERVICE_PREFFIX}{funcName}{IN_WEBWORKER_SUFFIX}(')
741
+
742
+ ignore1, ignore2, arguments = funcData[PROTOTYPE].partition('(')
743
+ put(f'{arguments};\n\n')
744
+
745
+ put(f'{SPACE}await {PROMISE_VARIABLE}.then(\n')
746
+ put(f'{SUBSPACE}{RESULT_VARIBLABLE} => ')
747
+ put('{ ')
748
+ put(f' {OUTPUT_VARIABLE} = {RESULT_VARIBLABLE};')
749
+ put(' },\n')
750
+ put(f'{SUBSPACE}{ERROR_VARIABLE} => ')
751
+ put('{ ')
752
+ put(' throw new Error (`Error: ${')
753
+ put(ERROR_VARIABLE)
754
+ put('}`); }\n')
755
+ put(f'{SPACE});\n\n')
756
+ put(f'{SPACE}return {OUTPUT_VARIABLE};\n')
757
+
758
+ def generateWasmInWebWorkerCall(put, funcName, funcData):
759
+ """
760
+ Generate code for wasm function call in the webworker.
761
+ """
762
+ # put annotation lines
763
+ for line in funcData[ANNOTATION]:
764
+ if ANNOT_NAME in line:
765
+ put(f'{line.rstrip()}{IN_WEBWORKER_SUFFIX}\n')
766
+ else:
767
+ put(f'{line}\n')
768
+
769
+ # put function prototype
770
+ putPrototypeForWebWorker(put, funcName, funcData)
771
+
772
+ # put main body
773
+ putMainBodyForWebWorker(put, funcName, funcData)
774
+
775
+ put('}\n\n')
776
+
777
+ with open(settings[PACKAGE_FILE], APPEND_MODE) as file:
778
+ put = file.write
779
+
780
+ # put import lines
781
+ putImportLines(put, settings, functionsData)
782
+
783
+ # put init-function
784
+ putInitFunction(put, settings)
785
+
786
+ # generate code for wasm-computations call
787
+ for cppFileName in functionsData.keys():
788
+ for funcName, funcData in functionsData[cppFileName].items():
789
+ generateWasmCall(put, funcName, funcData)
790
+ generateWasmInWebWorkerCall(put, funcName, funcData)
791
+
792
+ put('\n')
793
+
794
+ def main(nameOfSettingsFile="module.json"):
795
+ """
796
+ The main script:
797
+ 1) load export settings from json-file;
798
+ 2) parse C/C++-files and get C/C++-functions to be exported;
799
+ 3) create Emscripten command;
800
+ 4) execute Emscripten command;
801
+ 5) create JS-wrapper for wasm-functions run in webworkers;
802
+ 6) create worker files;
803
+ 7) complete JS-file created by Emscripten;
804
+ 8) create JS API file;
805
+ 9) generate code of wasm-computations call in package.ts;
806
+ 10) update the file package.json: add JS-file name created by Emscripten to 'sources'.
807
+ """
808
+ try:
809
+ # 1) load settings
810
+ settings = getSettings(nameOfSettingsFile)
811
+
812
+ # 2) parse C-files and get exported functions data
813
+ functionsData = getFunctionsFromListOfFiles(settings)
814
+
815
+ # write functions descriptors to json-file
816
+ saveFunctionsDataToFile(functionsData, 'func.json')
817
+
818
+ # 3) get command for Emscripten tool
819
+ command = getCommand(settings, functionsData)
820
+
821
+ # save command to file
822
+ saveCommand(command, 'command.txt')
823
+
824
+ # 4) execute command by Emscripten
825
+ os.system(command)
826
+
827
+ # 5) create JS-wrapper for wasm-functions run in webworkers
828
+ createJSwrapperForWasmInWebWorkerRun(settings)
829
+
830
+ # 6) create worker files
831
+ createWorkerFiles(settings, functionsData)
832
+
833
+ # 7) complete JS-file created by Emscripten
834
+ completeJsWasmfile(settings, functionsData)
835
+
836
+ # 8) create JS API file
837
+ generateApiFile(settings, functionsData)
838
+
839
+ # 9) generate code of wasm-computations call in package.ts
840
+ generateCodeInPackageTsFile(settings, functionsData)
841
+
842
+ # 10) update the file package.json
843
+ updatePackageJsonFile(settings)
844
+
845
+ # 11)* delete of the __pycache__ folder, otherwise it must be done further manually
846
+ shutil.rmtree('__pycache__')
847
+
848
+ except OSError:
849
+ print("OS ERROR: check paths and file names!")
850
+
851
+ except KeyError:
852
+ print("PARSING ERROR: check C/C++-code!")
853
+
854
+ except IndexError:
855
+ print("PARSING ERROR: check C/C++-code!")
856
+
857
+ except Warning:
858
+ print("Check annotation of exported C/C++-function!")
859
+
860
+
861
+ if __name__ == '__main__':
862
+ main()