Mercurial > hg > orthanc-stone
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