Skip to content

Commit 473939b

Browse files
reidspencerclaude
andcommitted
Make BASTLoader JS-compatible with platform-specific implementation
- Created BASTLoaderPlatform in jvm-native/ with blocking Await.result() - Created BASTLoaderPlatform in js/ returning error (no file I/O in browser) - Updated BASTLoader to delegate to platform-specific implementation - All 6 SharedBASTTest tests now pass on JVM, JS, and Native Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a0868dd commit 473939b

4 files changed

Lines changed: 92 additions & 36 deletions

File tree

NOTEBOOK.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ Benchmark on `dokn.riddl` (7.5KB, 167 nodes):
102102
|----------|--------|-------|
103103
| JVM | ✅ Passing | 6 tests in SharedBASTTest |
104104
| Native | ✅ Passing | 6 tests in SharedBASTTest |
105-
| JS | ⚠️ Known limitation | No local file I/O on browser platform |
105+
| JS | ✅ Passing | 6 tests in SharedBASTTest |
106106

107-
**Note**: JS cannot support BAST file loading because `DOMPlatformContext` throws `FileNotFoundException`
108-
for `file://` URLs. This is an inherent platform limitation, not a bug. BAST import loading is a JVM/Native feature.
107+
**Note**: BAST serialization/deserialization works on all platforms. BAST *file import loading* is
108+
JVM/Native only (JS returns error message since browser can't do local file I/O).
109109

110110
### Next Steps
111111

@@ -140,13 +140,13 @@ for `file://` URLs. This is an inherent platform limitation, not a bug. BAST imp
140140
**Completed**:
141141
- Created `SharedBASTTest.scala` in `passes/shared/src/test/` with 6 tests
142142
- Tests build AST programmatically (avoid parser's BAST import loading)
143-
- JVM: All 6 tests pass ✅
144-
- Native: All 6 tests pass ✅
145-
- JS: Known platform limitation (no local file I/O)
143+
- Made `BASTLoader` platform-aware with `BASTLoaderPlatform`:
144+
- JVM/Native: Uses blocking `Await.result()` for file loading
145+
- JS: Returns error message (file I/O not supported)
146+
- All platforms pass: JVM ✅, Native ✅, JS ✅
146147

147-
**Conclusion**: BAST serialization/deserialization works correctly on JVM and Native.
148-
JS cannot support BAST file loading because `DOMPlatformContext` throws `FileNotFoundException`
149-
for `file://` URLs - this is an inherent browser platform limitation, not a bug.
148+
**Key insight**: Separated blocking I/O code into `BASTLoaderPlatform` (in `jvm-native/` and `js/`)
149+
to allow JS linker to succeed while maintaining full functionality on JVM/Native.
150150

151151
### January 16, 2026 (Cleanup & Benchmarking)
152152

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2019-2026 Ossum, Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package com.ossuminc.riddl.language.bast
8+
9+
import com.ossuminc.riddl.language.AST.*
10+
import com.ossuminc.riddl.utils.{PlatformContext, URL}
11+
12+
/** JavaScript implementation of BAST loading - throws exception since file I/O is not supported. */
13+
private[bast] object BASTLoaderPlatform {
14+
15+
/** BAST import loading is not supported on JavaScript platform.
16+
*
17+
* The browser environment cannot perform local file I/O operations required
18+
* to load BAST files. This is an inherent platform limitation.
19+
*
20+
* @throws UnsupportedOperationException always
21+
*/
22+
def loadSingleImport(bi: BASTImport, baseURL: URL)(using pc: PlatformContext): Either[String, Nebula] = {
23+
Left("BAST import loading is not supported on JavaScript platform. " +
24+
"The browser cannot perform local file I/O operations.")
25+
}
26+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2019-2026 Ossum, Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package com.ossuminc.riddl.language.bast
8+
9+
import com.ossuminc.riddl.language.AST.*
10+
import com.ossuminc.riddl.language.Messages
11+
import com.ossuminc.riddl.utils.{PlatformContext, URL}
12+
13+
import scala.concurrent.{Await, ExecutionContext}
14+
import scala.concurrent.duration.*
15+
import scala.util.{Failure, Success, Try}
16+
17+
/** JVM/Native implementation of BAST loading with blocking I/O support. */
18+
private[bast] object BASTLoaderPlatform {
19+
20+
/** Load a single BAST import using blocking I/O.
21+
*
22+
* @param bi The BASTImport to load
23+
* @param baseURL The base URL for resolving relative paths
24+
* @param pc The platform context
25+
* @return Either an error message or the loaded Nebula
26+
*/
27+
def loadSingleImport(bi: BASTImport, baseURL: URL)(using pc: PlatformContext): Either[String, Nebula] = {
28+
Try {
29+
// Resolve the path relative to the base URL
30+
val bastURL = if URL.isValid(bi.path.s) then {
31+
URL(bi.path.s)
32+
} else {
33+
baseURL.parent.resolve(bi.path.s)
34+
}
35+
36+
// Load the BAST file bytes
37+
implicit val ec: ExecutionContext = pc.ec
38+
val future = pc.load(bastURL).map { data =>
39+
// Parse as BAST - note: data is loaded as String, convert to bytes
40+
val bytes = data.getBytes("ISO-8859-1") // Binary data preserved
41+
val reader = BASTReader(bytes)
42+
reader.read() // Returns Either[Messages, Nebula]
43+
}
44+
45+
// Wait for the result (with timeout) - blocking I/O
46+
Await.result(future, 30.seconds)
47+
} match {
48+
case Success(Right(nebula)) => Right(nebula)
49+
case Success(Left(msgs)) => Left(msgs.map(_.format).mkString("; "))
50+
case Failure(ex) => Left(ex.getMessage)
51+
}
52+
}
53+
}

language/shared/src/main/scala/com/ossuminc/riddl/language/bast/BASTLoader.scala

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import com.ossuminc.riddl.language.Messages.Messages
1212
import com.ossuminc.riddl.utils.{PlatformContext, URL}
1313

1414
import scala.collection.mutable
15-
import scala.concurrent.{Await, ExecutionContext}
16-
import scala.concurrent.duration.*
17-
import scala.util.{Failure, Success, Try}
1815

1916
/** Utility for loading BAST imports.
2017
*
@@ -78,37 +75,17 @@ object BASTLoader {
7875
}
7976

8077
/** Load a single BAST import.
78+
*
79+
* This delegates to the platform-specific implementation since JVM/Native
80+
* support blocking I/O while JavaScript does not.
8181
*
8282
* @param bi The BASTImport to load
8383
* @param baseURL The base URL for resolving relative paths
8484
* @param pc The platform context
8585
* @return Either an error message or the loaded Nebula
8686
*/
8787
private def loadSingleImport(bi: BASTImport, baseURL: URL)(using pc: PlatformContext): Either[String, Nebula] = {
88-
Try {
89-
// Resolve the path relative to the base URL
90-
val bastURL = if URL.isValid(bi.path.s) then {
91-
URL(bi.path.s)
92-
} else {
93-
baseURL.parent.resolve(bi.path.s)
94-
}
95-
96-
// Load the BAST file bytes
97-
implicit val ec: ExecutionContext = pc.ec
98-
val future = pc.load(bastURL).map { data =>
99-
// Parse as BAST - note: data is loaded as String, convert to bytes
100-
val bytes = data.getBytes("ISO-8859-1") // Binary data preserved
101-
val reader = BASTReader(bytes)
102-
reader.read() // Returns Either[Messages, Nebula]
103-
}
104-
105-
// Wait for the result (with timeout)
106-
Await.result(future, 30.seconds)
107-
} match {
108-
case Success(Right(nebula)) => Right(nebula)
109-
case Success(Left(msgs)) => Left(msgs.map(_.format).mkString("; "))
110-
case Failure(ex) => Left(ex.getMessage)
111-
}
88+
BASTLoaderPlatform.loadSingleImport(bi, baseURL)
11289
}
11390

11491
/** Check if a Root has any unloaded BASTImport nodes.

0 commit comments

Comments
 (0)