diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml
deleted file mode 100644
index 27873fa..0000000
--- a/.github/workflows/npm.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-name: NodeJS
-
-on:
- push:
- branches: [ "master" ]
- pull_request:
- branches: [ "master" ]
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
- matrix:
- node-version: [16.x, 18.x, 20.x]
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: Build
- run: |
- npm run test:js
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7ae27c3
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,41 @@
+name: test
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [16, 18, 20, 22]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Build bundle
+ run: |
+ npm run podman:build
+ npm run podman:bundle
+
+ - name: Run JavaScript tests
+ run: npx jest
+
+ - name: Run native Ruby tests
+ run: npm run test:rb
diff --git a/Gemfile b/Gemfile
index b5ef61a..d081a1b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,10 +6,10 @@ gem 'equivalent-xml', path: 'vendor/equivalent-xml'
gem 'ruby-ll', path: 'vendor/ruby-ll'
gem 'htmlentities', path: 'vendor/htmlentities'
gem 'opal', path: 'vendor/opal'
+gem 'plurimath', path: 'vendor/plurimath'
gem 'opal-rspec', '>= 1.1.0a'
-gem 'nokogiri'
gem 'rake'
gem 'rspec'
gem 'ae'
@@ -22,6 +22,7 @@ gem 'rack'
gem 'rackup'
gem 'simplecov'
gem 'ox'
-gem 'unitsml'
gem 'monitor'
-gem 'mml'
+gem 'unitsml', git: 'https://github.com/unitsml/unitsml-ruby.git', branch: 'fix/opal-database-payload-loader'
+gem 'lutaml-model', github: "lutaml/lutaml-model", branch: "fix/opal-runtime-compatibility-changes"
+gem 'omml', github: "plurimath/omml", branch: "fix/opal-runtime-compatibility-changes"
diff --git a/build.sh b/build.sh
index 7e395a2..73f71b9 100755
--- a/build.sh
+++ b/build.sh
@@ -1,17 +1,28 @@
#!/bin/sh
+set -e
+
rm -rf tmp/
mkdir -p tmp/
cp src/* tmp/
+echo "** Generating UnitsML Opal payload"
+bundle exec ruby scripts/generate_unitsml_opal_payload.rb tmp/unitsml_opal_payload.rb
+
echo "** Compiling Plurimath-Opal from Ruby with Opal"
bundle exec opal --esm -sjruby \
-qerb \
-rcorelib/array/pack \
-ropal-parser \
+ -qoga/setup_opal \
+ -qll/setup_opal \
+ -Itmp/ \
+ -runitsml_opal_payload \
+ -gplurimath \
-gunitsml \
-ghtmlentities \
-gparslet \
-gmonitor \
+ -snokogiri \
-sox \
-sox/ox \
-sox.so \
diff --git a/demo.html b/demo.html
index e64d57d..ce7e257 100644
--- a/demo.html
+++ b/demo.html
@@ -20,6 +20,7 @@
+
diff --git a/package.json b/package.json
index e647e9e..b28f1be 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,12 @@
"scripts": {
"test": "npm run test:js && npm run test:rb",
"test:js": "npm run build && jest",
- "test:rb": "podman run --rm -it -v $(pwd):/srv:z plurimath-js /bin/bash -c './setup.sh && env/plurimath'",
+ "test:rb": "npm run test:rb:native",
+ "test:rb:opal": "podman run --rm -i -e OPAL_PREFORK_DISABLE=1 -v .:/srv:z plurimath-js /bin/bash -c './setup.sh && env/plurimath'",
+ "test:rb:native": "podman run --rm -i -e OPAL_PREFORK_DISABLE=1 -v .:/srv:z plurimath-js /bin/bash -c './setup.sh && bundle exec rspec spec/unitsml_opal_payload_generator_spec.rb'",
"podman:build": "podman build -t plurimath-js .",
- "podman:shell": "podman run --rm -it -v $(pwd):/srv:z plurimath-js",
- "podman:bundle": "podman run --rm -it -v $(pwd):/srv:z plurimath-js /bin/bash -c './setup.sh && ./build.sh'",
+ "podman:shell": "podman run --rm -it -e OPAL_PREFORK_DISABLE=1 -v .:/srv:z plurimath-js",
+ "podman:bundle": "podman run --rm -i -e OPAL_PREFORK_DISABLE=1 -v .:/srv:z plurimath-js /bin/bash -c './setup.sh && ./build.sh'",
"build": "npm run podman:build && npm run podman:bundle",
"submodule:init": "git submodule init && npm run submodule:update",
"submodule:update": "git submodule update"
diff --git a/scripts/generate_unitsml_opal_payload.rb b/scripts/generate_unitsml_opal_payload.rb
new file mode 100644
index 0000000..438e88e
--- /dev/null
+++ b/scripts/generate_unitsml_opal_payload.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "ox"
+require "unitsdb"
+
+module PlurimathJs
+ module UnitsmlOpalPayloadGenerator
+ module_function
+
+ def build_payload_hash(database_path: nil)
+ source_path = database_path || Unitsdb.data_dir
+ ::Unitsdb::Database.from_db(source_path).to_hash
+ end
+
+ def to_ruby_source(database_hash)
+ rendered_hash = database_hash.inspect
+
+ <<~RUBY
+ # frozen_string_literal: true
+
+ require "unitsml"
+ require "unitsml/unitsdb"
+
+ Unitsml::Unitsdb::Database.load_opal_payload(
+ #{rendered_hash}.freeze,
+ )
+ RUBY
+ end
+
+ def write_to(file_path, database_hash: nil, database_path: nil)
+ payload_hash = database_hash || build_payload_hash(database_path: database_path)
+ absolute_path = File.expand_path(file_path)
+
+ File.write(absolute_path, to_ruby_source(payload_hash))
+ absolute_path
+ end
+ end
+end
+
+if $PROGRAM_NAME == __FILE__
+ output_path = ARGV[0]
+ database_path = ARGV[1]
+
+ abort "Usage: ruby scripts/generate_unitsml_opal_payload.rb OUTPUT_PATH [DATABASE_PATH]" unless output_path
+
+ PlurimathJs::UnitsmlOpalPayloadGenerator.write_to(
+ output_path,
+ database_path: database_path,
+ )
+end
diff --git a/setup.sh b/setup.sh
index bbaa05f..618b368 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,4 +1,5 @@
#!/bin/bash
+set -e
git submodule init
git submodule update
@@ -14,4 +15,3 @@ env/oga rake
rm -rf vendor/*/tmp/*
rm -rf vendor/ruby-ll/lib/libll.so vendor/oga/lib/liboga.so
-
diff --git a/spec/to-mathml-intent.spec.js b/spec/to-mathml-intent.spec.js
index ba8cfcb..f107423 100644
--- a/spec/to-mathml-intent.spec.js
+++ b/spec/to-mathml-intent.spec.js
@@ -1,9 +1,20 @@
import Plurimath from "../dist"
describe('to mathml with intent', () => {
- it('', () => {
+ it('adds MathML intent metadata when requested', () => {
const math = new Plurimath('∑_𝑥^𝑦 𝑧', 'unicode')
+ const mathml = math.toMathml(true).trim()
- expect(math.toMathml(true).trim()).toBe('')
+ expect(mathml).toContain('intent=":sum(𝑥,𝑦,$naryand)"')
+ expect(mathml).toContain('')
+ expect(mathml).toMatch(/(∑|∑)<\/mo>/)
+ expect(mathml).toMatch(/\s*(𝑧|𝑧)<\/mi>\s*<\/mrow>/)
+ })
+
+ it('omits MathML intent metadata by default', () => {
+ const math = new Plurimath('∑_𝑥^𝑦 𝑧', 'unicode')
+
+ expect(math.toMathml()).not.toContain(' intent=')
+ expect(math.toMathml()).not.toBe(math.toMathml(true))
})
})
diff --git a/spec/to-unicodemath.spec.js b/spec/to-unicodemath.spec.js
index 5de7b7f..6eaef3a 100644
--- a/spec/to-unicodemath.spec.js
+++ b/spec/to-unicodemath.spec.js
@@ -1,9 +1,21 @@
import Plurimath from "../dist"
describe('to unicodemath', () => {
- it('', () => {
+ it('matches conversion of UnicodeMath equation to UnicodeMath', () => {
const math = new Plurimath('∑_𝑥^𝑦 𝑧', 'unicode')
expect(math.toUnicodemath().trim()).toBe('∑_(𝑥)^(𝑦)▒〖𝑧〗')
})
+
+ it('matches conversion of LaTeX equation to UnicodeMath', () => {
+ const math = new Plurimath('M \\= \\begin{bmatrix} \\-\\sin λ_0 \\& \\cos λ_0 \\& 0 \\\\ \\-\\sin φ_0 \\cos λ_0 \\& \\-\\sin φ_0 \\sin λ_0 \\& \\cos φ_0 \\\\ \\cos φ_0 \\cos λ_0 \\& \\cos φ_0 \\sin λ_0 \\& \\sin φ_0 \\end{bmatrix}', 'latex')
+
+ expect(math.toUnicodemath()).toBe('M = ⓢ(− sinλ_(0)&cosλ_(0)&0@− sinφ_(0)cosλ_(0)&− sinφ_(0)sinλ_(0)&cosφ_(0)@cosφ_(0)cosλ_(0)&cosφ_(0)sinλ_(0)&sinφ_(0))')
+ })
+
+ it('tests basic equation performing plus operation', () => {
+ const math = new Plurimath('M \\= a \\+ b', 'latex')
+
+ expect(math.toUnicodemath()).toBe('M = a + b')
+ })
})
diff --git a/spec/unitsml.spec.js b/spec/unitsml.spec.js
new file mode 100644
index 0000000..c73e54c
--- /dev/null
+++ b/spec/unitsml.spec.js
@@ -0,0 +1,53 @@
+import Plurimath from "../dist"
+
+describe('unitsml asciimath', () => {
+ it('converts simple unit: kg', () => {
+ const math = new Plurimath('"unitsml(kg)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('rm(kg)')
+ expect(math.toLatex()).toBe('\\mathrm{kg}')
+ expect(math.toMathml()).toMatch(/\s*kg<\/mi>\s*<\/mstyle>/)
+ })
+
+ it('converts unit with value: 9.8 m/s^2', () => {
+ const math = new Plurimath('9.8 "unitsml(m/s^2)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('9.8 rm(m) * rm(s)^(- 2)')
+ expect(math.toLatex()).toBe('9.8 \\mathrm{m} \\cdot \\mathrm{s}^{- 2}')
+ })
+
+ it('converts frequency unit: Hz', () => {
+ const math = new Plurimath('"unitsml(Hz)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('rm(Hz)')
+ expect(math.toLatex()).toBe('\\mathrm{Hz}')
+ })
+
+ it('converts prefixed unit: MHz', () => {
+ const math = new Plurimath('"unitsml(MHz)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('rm(MHz)')
+ expect(math.toLatex()).toBe('\\mathrm{MHz}')
+ })
+
+ it('converts compound unit: g/mol', () => {
+ const math = new Plurimath('"unitsml(g/mol)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('rm(g) * rm(mol)^(- 1)')
+ expect(math.toLatex()).toBe('\\mathrm{g} \\cdot \\mathrm{mol}^{- 1}')
+ })
+
+ it('converts unit in expression context', () => {
+ const math = new Plurimath('1 "unitsml(A)"', 'asciimath')
+
+ expect(math.toAsciimath()).toBe('1 rm(A)')
+ expect(math.toLatex()).toBe('1 \\mathrm{A}')
+ })
+
+ it('converts Hz in complex expression', () => {
+ const math = new Plurimath('540 xx 10^(12) "unitsml(Hz)"', 'asciimath')
+
+ expect(math.toAsciimath()).toContain('rm(Hz)')
+ expect(math.toLatex()).toContain('\\mathrm{Hz}')
+ })
+})
diff --git a/spec/unitsml_opal_payload_generator_spec.rb b/spec/unitsml_opal_payload_generator_spec.rb
new file mode 100644
index 0000000..31445fc
--- /dev/null
+++ b/spec/unitsml_opal_payload_generator_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "tmpdir"
+require_relative "../scripts/generate_unitsml_opal_payload"
+
+RSpec.describe PlurimathJs::UnitsmlOpalPayloadGenerator do
+ let(:database_hash) do
+ {
+ "schema_version" => "2.0.0",
+ "units" => [],
+ "prefixes" => [],
+ "quantities" => [],
+ "dimensions" => [],
+ "unit_systems" => [],
+ }
+ end
+
+ describe ".to_ruby_source" do
+ it "renders a Ruby payload file that loads the Opal database payload" do
+ source = described_class.to_ruby_source(database_hash)
+
+ expect(source).to include('require "unitsml"')
+ expect(source).to include("Unitsml::Unitsdb::Database.load_opal_payload(")
+ expect(source).not_to include("class OpalDatabase")
+ expect(source).not_to include("def self.from_db")
+ expect(source).to match(/"schema_version"\s*=>\s*"2\.0\.0"/)
+ expect(source).to include(".freeze")
+ end
+
+ it "renders unit symbols as a Ruby literal" do
+ source = described_class.to_ruby_source(
+ database_hash.merge(
+ "units" => [
+ {
+ "id" => "ohm",
+ "symbols" => ["Ω", "µΩ"],
+ },
+ ],
+ ),
+ )
+
+ rendered_hash = source.match(/load_opal_payload\(\n (.*)\.freeze,/m)[1]
+ payload = eval(rendered_hash) # rubocop:disable Security/Eval
+
+ expect(payload["units"].first["symbols"]).to eq(["Ω", "µΩ"])
+ end
+ end
+
+ describe ".write_to" do
+ it "writes the generated payload file to the requested path" do
+ file_path = nil
+
+ Dir.mktmpdir do |dir|
+ file_path = described_class.write_to(
+ File.join(dir, "unitsml_opal_payload.rb"),
+ database_hash: database_hash,
+ )
+
+ generated_source = File.read(file_path)
+ expect(generated_source).to eq(described_class.to_ruby_source(database_hash))
+ end
+
+ expect(file_path).to end_with("unitsml_opal_payload.rb")
+ end
+ end
+end
diff --git a/src/index.ts b/src/index.ts
index 7149e27..e9410a5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,4 @@
import "./plurimath-opal.js";
-
Opal.require("plurimath");
export default class Plurimath {
diff --git a/vendor/plurimath b/vendor/plurimath
index 1474143..68564b2 160000
--- a/vendor/plurimath
+++ b/vendor/plurimath
@@ -1 +1 @@
-Subproject commit 1474143d3cb06f1744b7d79a61de1d346157f937
+Subproject commit 68564b20de4ade7c7ea60e6c3d62352489931df0