Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.20260528.2
0.20260529.0
5 changes: 5 additions & 0 deletions src/bindings/javascript/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,10 @@ void loggerApi()
Module["Issue"]["Type"] = Module["Issue.Type"];

delete Module["Issue.Type"];

Module["Issue"].prototype.toString = function()
{
return this.description;
};
});
}
42 changes: 42 additions & 0 deletions src/bindings/javascript/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,46 @@ EMSCRIPTEN_BINDINGS(libOpenCOR)
sedApi();
solverApi();
versionApi();

// Make all registered vectors behave like native JavaScript arrays: expose a .length property and [Symbol.iterator]
// so that for-of loops, spread syntax, and destructuring work without requiring callers to use .size()/.get().
// Note: the code within the EM_ASM() block is such that it doesn't rely on top-level comma-heavy literals since
// those commas can get interpreted as macro argument separators by the C++ preprocessor, which causes
// compilation errors.

EM_ASM({
let vectorNames = 'Doubles|FilePtrs|IssuePtrs|SedAbstractTaskPtrs|SedChangePtrs|SedDataDescriptionPtrs|SedDataGeneratorPtrs|SedInstanceTaskPtrs|SedModelPtrs|SedOutputPtrs|SedSimulationPtrs|SedStylePtrs|Strings'.split('|');

vectorNames.forEach(function(name) {
let prototype = Module[name].prototype;

Object.defineProperty(prototype, 'length', {
get: function() { return this.size(); }
});

prototype[Symbol.iterator] = function()
{
let i = 0;
let n = this.size();
let iterator = {};

iterator.next = () =>
{
let result = {};

if (i < n) {
result.value = this.get(i++);
result.done = false;
} else {
result.value = undefined;
result.done = true;
}

return result;
};

return iterator;
};
});
});
Comment thread
agarny marked this conversation as resolved.
}
5 changes: 5 additions & 0 deletions src/bindings/javascript/solver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ void solverApi()
Module["Solver"]["Type"] = Module["Solver.Type"];

delete Module["Solver.Type"];

Module["Solver"].prototype.toString = function()
{
return this.name;
};
});

// SolverOde API.
Expand Down
13 changes: 11 additions & 2 deletions src/bindings/python/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ void fileApi(nb::module_ &m)
.def_prop_ro("child_file_names", &libOpenCOR::File::childFileNames, "Return the child file names for this File object.")
.def_prop_ro("child_files", &libOpenCOR::File::childFiles, "Return the child files for this File object.")
.def("child_file", nb::overload_cast<size_t>(&libOpenCOR::File::childFile, nb::const_), "Get the requested child file for this File object.", nb::arg("index"))
.def("child_file", nb::overload_cast<const std::string &>(&libOpenCOR::File::childFile, nb::const_), "Get the requested child file for this File object.", nb::arg("file_name"));
.def("child_file", nb::overload_cast<const std::string &>(&libOpenCOR::File::childFile, nb::const_), "Get the requested child file for this File object.", nb::arg("file_name"))
.def("__repr__", [](const libOpenCOR::File &self) {
std::string loc = self.path().empty() ? self.url() : self.path();

return "File(\"" + loc + "\")";
});

// FileManager API.

Expand All @@ -61,5 +66,9 @@ void fileApi(nb::module_ &m)
.def_prop_ro("file_count", &libOpenCOR::FileManager::fileCount, "Return the number of managed files.")
.def_prop_ro("files", &libOpenCOR::FileManager::files, "Return the managed files.")
.def("file", nb::overload_cast<size_t>(&libOpenCOR::FileManager::file, nb::const_), "Get the requested managed file.", nb::arg("index"))
.def("file", nb::overload_cast<const std::string &>(&libOpenCOR::FileManager::file, nb::const_), "Get the requested managed file.", nb::arg("file_name_or_url"));
.def("file", nb::overload_cast<const std::string &>(&libOpenCOR::FileManager::file, nb::const_), "Get the requested managed file.", nb::arg("file_name_or_url"))
.def("__len__", &libOpenCOR::FileManager::fileCount)
.def("__iter__", [](const libOpenCOR::FileManager &self) {
return nb::cast(self.files()).attr("__iter__")();
});
}
8 changes: 7 additions & 1 deletion src/bindings/python/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,11 @@ void loggerApi(nb::module_ &m)

issue.def_prop_ro("type", &libOpenCOR::Issue::type, "Get the type of this Issue object.")
.def_prop_ro("type_as_string", &libOpenCOR::Issue::typeAsString, "Get the type of this Issue object as a string.")
.def_prop_ro("description", &libOpenCOR::Issue::description, "Get the description for this Issue object.");
.def_prop_ro("description", &libOpenCOR::Issue::description, "Get the description for this Issue object.")
.def("__repr__", [](const libOpenCOR::Issue &self) {
return "Issue(" + self.typeAsString() + ": \"" + self.description() + "\")";
})
.def("__str__", [](const libOpenCOR::Issue &self) {
return self.description();
});
}
6 changes: 5 additions & 1 deletion src/bindings/python/sed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ void sedApi(nb::module_ &m)
.def_prop_ro("has_tasks", &libOpenCOR::SedInstance::hasTasks, "Return whether there are some tasks.")
.def_prop_ro("task_count", &libOpenCOR::SedInstance::taskCount, "Return the number of tasks.")
.def_prop_ro("tasks", &libOpenCOR::SedInstance::tasks, "Return the tasks.")
.def("task", &libOpenCOR::SedInstance::task, "Return the task.");
.def("task", &libOpenCOR::SedInstance::task, "Return the task.")
.def("__len__", &libOpenCOR::SedInstance::taskCount)
.def("__iter__", [](const libOpenCOR::SedInstance &self) {
return nb::cast(self.tasks()).attr("__iter__")();
});

// SedInstanceTask API.

Expand Down
5 changes: 4 additions & 1 deletion src/bindings/python/solver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ void solverApi(nb::module_ &m)

solver.def_prop_ro("type", &libOpenCOR::Solver::type, "Get the type of the Solver object.")
.def_prop_ro("id", &libOpenCOR::Solver::id, "Get the (KiSAO) id of the Solver object.")
.def_prop_ro("name", &libOpenCOR::Solver::name, "Get the name of the Solver object.");
.def_prop_ro("name", &libOpenCOR::Solver::name, "Get the name of the Solver object.")
.def("__repr__", [](const libOpenCOR::Solver &self) {
return "Solver(name=\"" + self.name() + "\")";
});

// SolverOde API.

Expand Down
12 changes: 6 additions & 6 deletions tests/bindings/javascript/file.basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ test.describe('File basic tests', () => {

assert.strictEqual(fileManager.hasFiles, false);
assert.strictEqual(fileManager.fileCount, 0);
assert.strictEqual(fileManager.files.size(), 0);
assert.strictEqual(fileManager.files.length, 0);
assert.strictEqual(fileManager.file(0), null);
assert.strictEqual(fileManager.fileFromFileNameOrUrl(utils.LOCAL_FILE), null);

Expand All @@ -98,39 +98,39 @@ test.describe('File basic tests', () => {

assert.strictEqual(sameFileManager.hasFiles, true);
assert.strictEqual(sameFileManager.fileCount, 1);
assert.strictEqual(sameFileManager.files.size(), 1);
assert.strictEqual(sameFileManager.files.length, 1);
assert.deepStrictEqual(fileManager.file(0), localFile);
assert.deepStrictEqual(sameFileManager.fileFromFileNameOrUrl(utils.LOCAL_FILE), localFile);

const remoteFile = new loc.File(utils.REMOTE_FILE);

assert.strictEqual(fileManager.hasFiles, true);
assert.strictEqual(fileManager.fileCount, 2);
assert.strictEqual(fileManager.files.size(), 2);
assert.strictEqual(fileManager.files.length, 2);
assert.deepStrictEqual(fileManager.file(1), remoteFile);
assert.deepStrictEqual(fileManager.fileFromFileNameOrUrl(utils.REMOTE_FILE), remoteFile);

sameFileManager.unmanage(localFile);

assert.strictEqual(sameFileManager.hasFiles, true);
assert.strictEqual(sameFileManager.fileCount, 1);
assert.strictEqual(sameFileManager.files.size(), 1);
assert.strictEqual(sameFileManager.files.length, 1);
assert.deepStrictEqual(fileManager.file(1), null);
assert.deepStrictEqual(sameFileManager.fileFromFileNameOrUrl(utils.LOCAL_FILE), null);

sameFileManager.manage(localFile);

assert.strictEqual(sameFileManager.hasFiles, true);
assert.strictEqual(sameFileManager.fileCount, 2);
assert.strictEqual(sameFileManager.files.size(), 2);
assert.strictEqual(sameFileManager.files.length, 2);
assert.deepStrictEqual(fileManager.file(1), localFile);
assert.deepStrictEqual(sameFileManager.fileFromFileNameOrUrl(utils.LOCAL_FILE), localFile);

fileManager.reset();

assert.strictEqual(fileManager.hasFiles, false);
assert.strictEqual(fileManager.fileCount, 0);
assert.strictEqual(fileManager.files.size(), 0);
assert.strictEqual(fileManager.files.length, 0);
assert.deepStrictEqual(fileManager.file(0), null);
assert.deepStrictEqual(fileManager.file(1), null);
assert.deepStrictEqual(fileManager.fileFromFileNameOrUrl(utils.REMOTE_FILE), null);
Expand Down
8 changes: 4 additions & 4 deletions tests/bindings/javascript/file.child.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ test.describe('File type tests', () => {

assert.strictEqual(file.hasChildFiles, true);
assert.strictEqual(file.childFileCount, specificChildFileNames.length + 1);
assert.strictEqual(file.childFileNames.size(), specificChildFileNames.length + 1);
assert.strictEqual(file.childFiles.size(), specificChildFileNames.length + 1);
assert.strictEqual(file.childFileNames.length, specificChildFileNames.length + 1);
assert.strictEqual(file.childFiles.length, specificChildFileNames.length + 1);

let index = -1;
const simulationFile = file.childFileFromFileName('simulation.json');
Expand All @@ -59,8 +59,8 @@ test.describe('File type tests', () => {

assert.strictEqual(file.hasChildFiles, false);
assert.strictEqual(file.childFileCount, 0);
assert.strictEqual(file.childFileNames.size(), 0);
assert.strictEqual(file.childFiles.size(), 0);
assert.strictEqual(file.childFileNames.length, 0);
assert.strictEqual(file.childFiles.length, 0);
assert.strictEqual(file.childFile(0), null);
assert.strictEqual(file.childFileFromFileName(utils.UNKNOWN_FILE), null);
});
Expand Down
17 changes: 14 additions & 3 deletions tests/bindings/javascript/logger.coverage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test.describe('Issue coverage tests', () => {

file.setContents(utils.CELLML_CONTENTS);

assert.strictEqual(file.issues.size(), 0);
assert.strictEqual(file.issues.length, 0);
});

test('issue()', () => {
Expand Down Expand Up @@ -81,7 +81,7 @@ test.describe('Issue coverage tests', () => {

file.setContents(utils.CELLML_CONTENTS);

assert.strictEqual(file.errors.size(), 0);
assert.strictEqual(file.errors.length, 0);
});

test('error()', () => {
Expand Down Expand Up @@ -114,7 +114,7 @@ test.describe('Issue coverage tests', () => {

file.setContents(utils.CELLML_CONTENTS);

assert.strictEqual(file.warnings.size(), 0);
assert.strictEqual(file.warnings.length, 0);
});

test('warning()', () => {
Expand All @@ -127,4 +127,15 @@ test.describe('Issue coverage tests', () => {
assert.notStrictEqual(document.warning(0), null);
assert.strictEqual(document.warning(document.warningCount), null);
});

test('toString()', () => {
const file = new loc.File(utils.ERROR_CELLML_FILE);

file.setContents(utils.fileContents(utils.resourcePath('error.cellml')));

const issue = file.issue(0);

assert.notStrictEqual(issue, null);
assert.strictEqual(String(issue), issue.description);
});
});
12 changes: 5 additions & 7 deletions tests/bindings/javascript/res/res/libopencor.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@ function showIssues(issues) {
issuesElement.empty();

if (Array.isArray(issues)) {
for (let i = 0; i < issues.length; ++i) {
issuesElement.append(`<li><span·class="bold">Error:</span>·${formattedIssueDescription(issues[i])}</li>`);
for (const issue of issues) {
issuesElement.append(`<li><span·class="bold">Error:</span>·${formattedIssueDescription(issue)}</li>`);
}
} else {
for (let i = 0; i < issues.size(); ++i) {
const issue = issues.get(i);

for (const issue of issues) {
issuesElement.append(
'<li><span class="bold">' +
issue.typeAsString +
Expand All @@ -61,8 +59,8 @@ function listFiles() {

const files = fileManager.files;

for (let i = 0; i < files.size(); ++i) {
console.log(` - ${files.get(i).fileName}`);
for (const file of files) {
console.log(` - ${file.fileName}`);
}
} else {
console.log('No files.');
Expand Down
Loading
Loading