changeset 496:8b6ceae45ba0 bgo-commands-codegen

Finished (untested) C++, html, typescript, tsc & browserify production.
author bgo-osimis
date Sat, 23 Feb 2019 15:04:29 +0100
parents 6405435480ae
children d79f78971fae
files .hgignore Resources/CodeGeneration/stonegentool.py Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt Resources/CodeGeneration/testWasmIntegrated/DefaultLibrary.js Resources/CodeGeneration/testWasmIntegrated/build.sh Resources/CodeGeneration/testWasmIntegrated/main.cpp Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.html Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml
diffstat 9 files changed, 225 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Feb 23 14:14:32 2019 +0100
+++ b/.hgignore	Sat Feb 23 15:04:29 2019 +0100
@@ -17,3 +17,5 @@
 Resources/CodeGeneration/testCppHandler/build_msbuild/
 syntax: glob
 Resources/CodeGeneration/testWasmIntegrated/build-wasm/
+Resources/CodeGeneration/testWasmIntegrated/build-tsc/
+Resources/CodeGeneration/testWasmIntegrated/build-final/
--- a/Resources/CodeGeneration/stonegentool.py	Sat Feb 23 14:14:32 2019 +0100
+++ b/Resources/CodeGeneration/stonegentool.py	Sat Feb 23 15:04:29 2019 +0100
@@ -4,19 +4,6 @@
 import os
 import sys
 from jinja2 import Template
-from typing import (
-    Any,
-    Dict,
-    Generator,
-    Iterable,
-    Iterator,
-    List,
-    Match,
-    Optional,
-    Tuple,
-    Union,
-    cast,
-)
 from io import StringIO
 import time
 
@@ -71,7 +58,7 @@
         self.tsDispatcher = StringIO()
         self.tsHandler = StringIO()
 
-    def FlattenToFiles(self, outputDir: str):
+    def FlattenToFiles(self, outputDir):
         raise NotImplementedError()
 
 
@@ -106,16 +93,16 @@
         return json.loads(fileContent)
 
 
-def LoadSchemaFromJson(filePath: str):
+def LoadSchemaFromJson(filePath):
     return JsonHelpers.loadJsonWithComments(filePath)
 
-def CanonToCpp(canonicalTypename: str) -> str:
+def CanonToCpp(canonicalTypename):
   # C++: prefix map vector and string with std::map, std::vector and
   # std::string
   # replace int32 by int32_t
   # replace float32 by float
   # replace float64 by double
-  retVal: str = canonicalTypename
+  retVal = canonicalTypename
   retVal = retVal.replace("map", "std::map")
   retVal = retVal.replace("vector", "std::vector")
   retVal = retVal.replace("string", "std::string")
@@ -124,13 +111,13 @@
   retVal = retVal.replace("float64", "double")
   return retVal
 
-def CanonToTs(canonicalTypename: str) -> str:
+def CanonToTs(canonicalTypename):
   # TS: replace vector with Array and map with Map
   # string remains string
   # replace int32 by number
   # replace float32 by number
   # replace float64 by number
-  retVal: str = canonicalTypename
+  retVal = canonicalTypename
   retVal = retVal.replace("map", "Map")
   retVal = retVal.replace("vector", "Array")
   retVal = retVal.replace("int32", "number")
@@ -139,7 +126,7 @@
   retVal = retVal.replace("bool", "boolean")
   return retVal
 
-def NeedsTsConstruction(enums: Dict, tsType: str):
+def NeedsTsConstruction(enums, tsType):
   if tsType == 'boolean':
     return False
   elif tsType == 'number':
@@ -176,14 +163,14 @@
   return MakeTemplate(templateFileContents)
   templateFile.close()
 
-def EatToken(sentence: str) -> Tuple[str, str]:
+def EatToken(sentence):
     """splits "A,B,C" into "A" and "B,C" where A, B and C are type names
   (including templates) like "int32", "TotoTutu", or 
   "map<map<int32,vector<string>>,map<string,int32>>" """
 
     if sentence.count("<") != sentence.count(">"):
         raise Exception(
-            f"Error in the partial template type list {sentence}."
+            "Error in the partial template type list " + str(sentence) + "."
             + " The number of < and > do not match!"
         )
 
@@ -199,7 +186,7 @@
     return (sentence, "")
 
 
-def SplitListOfTypes(typename: str) -> List[str]:
+def SplitListOfTypes(typename):
     """Splits something like
   vector<string>,int32,map<string,map<string,int32>> 
   in:
@@ -209,7 +196,7 @@
   
   This is not possible with a regex so 
   """
-    stillStuffToEat: bool = True
+    stillStuffToEat = True
     tokenList = []
     restOfString = typename
     while stillStuffToEat:
@@ -224,7 +211,7 @@
   re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>")
 
 
-def ParseTemplateType(typename) -> Tuple[bool, str, List[str]]:
+def ParseTemplateType(typename):
     """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", 
     then it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
   otherwise it returns (false,"","")"""
@@ -237,7 +224,7 @@
     if matches == None:
         return (False, "", [])
     else:
-        m = cast(Match[str], matches)
+        m = matches
         assert len(m.groups()) == 2
         # we need to split with the commas that are outside of the
         # defined types. Simply splitting at commas won't work
@@ -246,14 +233,14 @@
 
 
 def ComputeOrderFromTypeTree(
-  ancestors: List[str], 
-  genOrder: List[str], 
-  shortTypename: str, schema: Dict[str, Dict]) -> None:
+  ancestors, 
+  genOrder, 
+  shortTypename, schema):
 
   if shortTypename in ancestors:
     raise Exception(
-      f"Cyclic dependency chain found: the last of {ancestors} "
-      + f"depends on {shortTypename} that is already in the list."
+      "Cyclic dependency chain found: the last of " + str(ancestors) +
+      + " depends on " + str(shortTypename) + " that is already in the list."
     )
 
   if not (shortTypename in genOrder):
@@ -273,7 +260,7 @@
       # dependency that we must take into account in the dep graph,
       # i.e., a struct.
       if IsShortStructType(shortTypename, schema):
-        struct:Dict = schema[GetLongTypename(shortTypename, schema)]
+        struct = schema[GetLongTypename(shortTypename, schema)]
         # The keys in the struct dict are the member names
         # The values in the struct dict are the member types
         for field in struct.keys():
@@ -294,19 +281,19 @@
 # |   Utility functions   |
 # +-----------------------+
 
-def IsShortStructType(typename: str, schema: Dict[str, Dict]) -> bool:
+def IsShortStructType(typename, schema):
   fullStructName = "struct " + typename
   return (fullStructName in schema)
 
-def GetLongTypename(shortTypename: str, schema: Dict):
+def GetLongTypename(shortTypename, schema):
   if shortTypename.startswith("enum "):
     raise RuntimeError('shortTypename.startswith("enum "):')
-  enumName: str = "enum " + shortTypename
+  enumName = "enum " + shortTypename
   isEnum = enumName in schema
 
   if shortTypename.startswith("struct "):
     raise RuntimeError('shortTypename.startswith("struct "):')
-  structName: str = "struct " + shortTypename
+  structName = "struct " + shortTypename
   isStruct = ("struct " + shortTypename) in schema
 
   if isEnum and isStruct:
@@ -317,16 +304,16 @@
   if isStruct:
     return structName
 
-def IsTypename(fullName: str) -> bool:
+def IsTypename(fullName):
   return (fullName.startswith("enum ") or fullName.startswith("struct "))
 
-def IsEnumType(fullName: str) -> bool:
+def IsEnumType(fullName):
   return fullName.startswith("enum ")
 
-def IsStructType(fullName: str) -> bool:
+def IsStructType(fullName):
   return fullName.startswith("struct ")
 
-def GetShortTypename(fullTypename: str) -> str:
+def GetShortTypename(fullTypename):
   if fullTypename.startswith("struct "):
     return fullTypename[7:] 
   elif fullTypename.startswith("enum"):
@@ -335,14 +322,14 @@
     raise RuntimeError \
       ('fullTypename should start with either "struct " or "enum "')
 
-def CheckSchemaSchema(schema: Dict) -> None:
+def CheckSchemaSchema(schema):
   if not "rootName" in schema:
       raise Exception("schema lacks the 'rootName' key")
   for name in schema.keys():
     if (not IsEnumType(name)) and (not IsStructType(name)) and \
       (name != 'rootName'):
       raise RuntimeError \
-        (f'Type "{name}" should start with "enum " or "struct "')
+        ('Type "' + str(name) + '" should start with "enum " or "struct "')
 
   # TODO: check enum fields are unique (in whole namespace)
   # TODO: check struct fields are unique (in each struct)
@@ -352,7 +339,7 @@
 # | Main processing logic |
 # +-----------------------+
 
-def ComputeRequiredDeclarationOrder(schema: dict) -> List[str]:
+def ComputeRequiredDeclarationOrder(schema):
   # sanity check
   CheckSchemaSchema(schema)
 
@@ -364,25 +351,25 @@
   # We do not care about the enums here... They do not depend upon
   # anything and we'll handle them, in their original declaration 
   # order, at the start
-  genOrder: List = []
+  genOrder = []
   for fullName in schema.keys():
     if IsStructType(fullName):
-      realName: str = GetShortTypename(fullName)
-      ancestors: List[str] = []
+      realName = GetShortTypename(fullName)
+      ancestors = []
       ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
   return genOrder
 
-def ProcessSchema(schema: dict, genOrder: List[str]) -> Dict:
+def ProcessSchema(schema, genOrder):
   # sanity check
   CheckSchemaSchema(schema)
 
   # let's doctor the schema to clean it up a bit
   # order DOES NOT matter for enums, even though it's a list
-  enums: List[Dict] = []
+  enums = []
   for fullName in schema.keys():
     if IsEnumType(fullName):
       # convert "enum Toto" to "Toto"
-      typename:str = GetShortTypename(fullName)
+      typename = GetShortTypename(fullName)
       enum = {}
       enum['name'] = typename
       assert(type(schema[fullName]) == list)
@@ -409,7 +396,7 @@
   #   }
   # ]
 
-  structs: List[Dict] = []
+  structs = []
   for i in range(len(genOrder)):
     # this is already the short name
     typename = genOrder[i]
@@ -456,7 +443,7 @@
 
 def GetTemplatingDictFromSchemaFilename(fn):
   obj = LoadSchema(fn)
-  genOrder: str = ComputeRequiredDeclarationOrder(obj)
+  genOrder = ComputeRequiredDeclarationOrder(obj)
   templatingDict = ProcessSchema(obj, genOrder)
   return templatingDict
 
@@ -497,27 +484,26 @@
   schemaFile = args.input_schema
   outDir = args.out_dir
 
-  tdico: Dict = GetTemplatingDictFromSchemaFilename(schemaFile)
+  tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
 
   tsTemplateFile = \
     os.path.join(os.path.dirname(__file__), 'template.in.ts')
   template = MakeTemplateFromFile(tsTemplateFile)
-  renderedTsCode: str = template.render(**tdico)
+  renderedTsCode = template.render(**tdico)
   outputTsFile = os.path.join( \
-    outDir,f"{tdico['rootName']}_generated.ts")
+    outDir,str(tdico['rootName']) + "_generated.ts")
   with open(outputTsFile,"wt",encoding='utf8') as outFile:
     outFile.write(renderedTsCode)
 
   cppTemplateFile = \
     os.path.join(os.path.dirname(__file__), 'template.in.h')
   template = MakeTemplateFromFile(cppTemplateFile)
-  renderedCppCode: str = template.render(**tdico)
+  renderedCppCode = template.render(**tdico)
   outputCppFile = os.path.join( \
-    outDir,f"{tdico['rootName']}_generated.hpp")
+    outDir, str(tdico['rootName']) + "_generated.hpp")
   with open(outputCppFile,"wt",encoding='utf8') as outFile:
     outFile.write(renderedCppCode)
 
-
 # def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None:
 #   """Writes the enumerations in genc"""
 #   enumDict:Dict=schema[fullName]
--- a/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt	Sat Feb 23 14:14:32 2019 +0100
+++ b/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt	Sat Feb 23 15:04:29 2019 +0100
@@ -2,6 +2,16 @@
 
 project(testWasmIntegratedCpp)
 
+set(WASM_FLAGS "-s WASM=1 -O0 -g0")
+set(WASM_MODULE_NAME "testWasmIntegrated" CACHE STRING "Name of the WebAssembly module")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js  -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_CURRENT_LIST_DIR}/DefaultLibrary.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000")  # 512MB + resize
+
+add_definitions(-DORTHANC_ENABLE_WASM=1)
+
 set(testWasmIntegratedCpp_Codegen_Deps
   ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml 
   ${CMAKE_CURRENT_LIST_DIR}/../template.in.h
@@ -10,11 +20,11 @@
 
 add_custom_command(
     OUTPUT  ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.ts
-    COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/../test_data/testWasmIntegratedCpp_api.yaml
+    COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml
     DEPENDS ${testCppHandler_Codegen_Deps}
 )
 
-add_library(testWasmIntegratedCpp main.cpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${testCppHandler_Codegen_Deps})
+add_executable(testWasmIntegratedCpp main.cpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${testCppHandler_Codegen_Deps})
 
 target_include_directories(testWasmIntegratedCpp  PUBLIC ${CMAKE_BINARY_DIR})
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/testWasmIntegrated/DefaultLibrary.js	Sat Feb 23 15:04:29 2019 +0100
@@ -0,0 +1,11 @@
+// this file contains the JS method you want to expose to C++ code
+
+mergeInto(LibraryManager.library, {
+  // each time the Application updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface
+  // It needs to be put in this file so that the emscripten SDK linker knows where to find it.
+  UpdateApplicationStatusFromCpp: function(statusUpdateMessage) {
+    var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage);
+    UpdateWebApplication(statusUpdateMessage_);
+  }
+});
+  
\ No newline at end of file
--- a/Resources/CodeGeneration/testWasmIntegrated/build.sh	Sat Feb 23 14:14:32 2019 +0100
+++ b/Resources/CodeGeneration/testWasmIntegrated/build.sh	Sat Feb 23 15:04:29 2019 +0100
@@ -15,16 +15,16 @@
 
 cd ..
 
-mkdir -p 
+mkdir -p build-final
 
 # compile TS to JS
 tsc --module commonjs --sourceMap -t ES2015 --outDir "build-tsc/" build-wasm/testWasmIntegratedCpp_generated.ts testWasmIntegrated.ts 
 
 # bundle all JS files to final build dir 
-browserify "build-wasm/testWasmIntegratedCpp.js" "build-tsc/testWasmIntegratedCpp_generated.js" "build-tsc/testWasmIntegrated.js" -o "testWasmIntegratedApp.js"
+browserify "build-wasm/testWasmIntegratedCpp.js" "build-tsc/build-wasm/testWasmIntegratedCpp_generated.js" "build-tsc/testWasmIntegrated.js" -o "build-final/testWasmIntegratedApp.js"
 
 # copy HTML start page to output dir
-cp index.html build-final/
+cp testWasmIntegrated.html build-final/
 
 # copy WASM binary to output dir
 cp build-wasm/testWasmIntegratedCpp.wasm  build-final/
--- a/Resources/CodeGeneration/testWasmIntegrated/main.cpp	Sat Feb 23 14:14:32 2019 +0100
+++ b/Resources/CodeGeneration/testWasmIntegrated/main.cpp	Sat Feb 23 15:04:29 2019 +0100
@@ -1,6 +1,30 @@
 #include <iostream>
+#include <emscripten/emscripten.h>
 
 int main()
 {
     std::cout << "Hello world from testWasmIntegrated!" << std::endl;
-}
\ No newline at end of file
+}
+
+void EMSCRIPTEN_KEEPALIVE StartWasmApplication(const char* baseUri)
+{
+    printf("StartWasmApplication\n");
+
+//     // recreate a command line from uri arguments and parse it
+//     boost::program_options::variables_map parameters;
+//     boost::program_options::options_description options;
+//     application->DeclareStartupOptions(options);
+//     startupParametersBuilder.GetStartupParameters(parameters, options);
+
+//     context.reset(new OrthancStone::StoneApplicationContext(broker));
+//     context->SetOrthancBaseUrl(baseUri);
+//     printf("Base URL to Orthanc API: [%s]\n", baseUri);
+//     context->SetWebService(OrthancStone::WasmWebService::GetInstance());
+//     context->SetDelayedCallExecutor(OrthancStone::WasmDelayedCallExecutor::GetInstance());
+//     application->Initialize(context.get(), statusBar_, parameters);
+//     application->InitializeWasm();
+
+// //    viewport->SetSize(width_, height_);
+//     printf("StartWasmApplication - completed\n");
+  }
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.html	Sat Feb 23 15:04:29 2019 +0100
@@ -0,0 +1,22 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+
+    <title>Simple Viewer</title>
+    <link href="styles.css" rel="stylesheet" />
+
+<body>
+  <div id="toolbox" style="height: 50px">
+    <button tool-selector="line-measure" class="tool-selector">line</button>
+  </div>
+  <script type="text/javascript" src="testWasmIntegratedApp.js"></script>
+</body>
+
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts	Sat Feb 23 15:04:29 2019 +0100
@@ -0,0 +1,58 @@
+export var SendMessageToStoneApplication: Function = null;
+
+function SelectTool(toolName: string) {
+  var command = {
+    command: "selectTool:" + toolName,
+    commandType: "generic-no-arg-command",
+    args: {
+    }                                                                                                                       
+  };
+  SendMessageToStoneApplication(JSON.stringify(command));
+}
+
+(<any> window).StoneFrameworkModule = {
+  preRun: [ 
+    function() {
+    console.log('Loading the Stone Framework using WebAssembly');
+    }
+  ],
+  postRun: [ 
+    function()  {
+    // This function is called by ".js" wrapper once the ".wasm"
+    // WebAssembly module has been loaded and compiled by the
+    // browser
+    console.log('WebAssembly is ready');
+    SendMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']);
+    }
+  ],
+  print: function(text : string) {
+    console.log(text);
+  },
+  printErr: function(text : string) {
+    console.error(text);
+  },
+  totalDependencies: 0
+};
+
+// install "SelectTool" handlers
+document.querySelectorAll("[tool-selector]").forEach((e) => {
+    (e as HTMLButtonElement).addEventListener("click", () => {
+    SelectTool(e.attributes["tool-selector"].value);
+    });
+});
+
+// this method is called "from the C++ code" when the StoneApplication is updated.
+// it can be used to update the UI of the application
+function UpdateWebApplication(statusUpdateMessageString: string) {
+  console.log("updating web application: ", statusUpdateMessageString);
+  let statusUpdateMessage = JSON.parse(statusUpdateMessageString);
+
+  if ("event" in statusUpdateMessage)
+  {
+    let eventName = statusUpdateMessage["event"];
+    if (eventName == "appStatusUpdated")
+    {
+      //ui.onAppStatusUpdated(statusUpdateMessage["data"]);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml	Sat Feb 23 15:04:29 2019 +0100
@@ -0,0 +1,49 @@
+#
+#        1         2         3         4         5         6         7         8
+# 345678901234567890123456789012345678901234567890123456789012345678901234567890
+#
+rootName: testWasmIntegratedCpp
+
+struct B:
+  someAs: vector<A>
+  someInts: vector<int32>
+
+struct C:
+  someBs: vector<B>
+  ddd:    vector<string>
+
+struct A:
+  someStrings: vector<string>
+  someInts2: vector<int32>
+  movies: vector<MovieType>
+
+struct Message1:
+  a: int32
+  b: string
+  c: EnumMonth0
+  d: bool
+
+struct Message2:
+  toto: string
+  tata: vector<Message1>
+  tutu: vector<string>
+  titi: map<string, string>
+  lulu: map<string, Message1>
+  movieType: MovieType
+
+enum MovieType:
+  - RomCom
+  - Horror
+  - ScienceFiction
+  - Vegetables
+
+enum CrispType:
+  - SaltAndPepper
+  - CreamAndChives
+  - Paprika
+  - Barbecue
+
+enum EnumMonth0:
+  - January
+  - February
+  - March