Skip to content
Draft
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
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import sbtcrossproject.CrossPlugin.autoImport.CrossType
import sbtcrossproject.CrossProject
import org.scalajs.sbtplugin.ScalaJSCrossVersion

val nlDependencyVersion = "7.0.0-beta2-8cd3e65"
val nlDependencyVersion = sys.env.getOrElse("NL_DEPENDENCY_VERSION", "7.0.0-beta2-8cd3e65")

val parserJsDependencyVersion = "0.4.0-8cd3e65"
val parserJsDependencyVersion = sys.env.getOrElse("PARSER_JS_DEPENDENCY_VERSION", "0.4.0-8cd3e65")

val scalazVersion = "7.2.36"

Expand Down
20 changes: 20 additions & 0 deletions compiler/js/src/main/scala/BrowserCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import json.TortoiseJson._
import json.WidgetToJson

import org.nlogo.core.{ CompilerException, Plot }
import org.nlogo.parse.FrontEnd

import org.nlogo.tortoise.compiler.xml.TortoiseModelLoader

import org.nlogo.parse.CompilerUtilities
Expand All @@ -30,6 +32,7 @@ import scalaz.std.list._
import scalaz.Scalaz.ToValidationOps
import scalaz.Validation.FlatMap.ValidationFlatMapRequested


// scalastyle:off number.of.methods
@JSExportTopLevel("BrowserCompiler")
class BrowserCompiler {
Expand Down Expand Up @@ -220,6 +223,23 @@ class BrowserCompiler {

}

@JSExport
def listExtensions(source: String): NativeJson = {
val extensions: Seq[(String, Option[String])] = FrontEnd.findAllExtensions(source);
val json = JsArray(
extensions.map {
case (name, url) =>
JsObject(
ListMap("name" -> JsString(name), "url" -> (url match {
case Some(u) => JsString(u)
case None => JsNull
}))
)
}
)
JsonLibrary.toNative(json)
}

@JSExport
def listProcedures(): NativeJson = {

Expand Down
39 changes: 39 additions & 0 deletions compiler/js/src/main/scala/NLWExtensionsLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This file is related to the NetLogo Web (NLW) project
// Please see org:NetLogo/Galapagos/app/assets/javascript/beak/nlw-extensions-loader.js
// for more details. This change was introduced in https://github.com/NetLogo/NetLogo/pull/2508
// Omar Ibrahim, 2025
package org.nlogo.tortoise.compiler

import scala.scalajs.js
import scala.scalajs.js.annotation.JSGlobal
import play.api.libs.json.{ JsValue, Json }

// window.NLWExtensionsLoader is a global object and is
// gauranteed to be available before
@js.native
@JSGlobal("NLWExtensionsLoader")
object JSNLWExtensionsLoader extends js.Object {
def getPrimitivesFromURL(url: String): js.UndefOr[js.Object] = js.native
def appendURLProtocol(url: String): String = js.native
def validateURL(url: String): Boolean = js.native
}

object NLWExtensionsLoader {
def getPrimitivesFromURL(url: String): Option[JsValue] = {
val result = JSNLWExtensionsLoader.getPrimitivesFromURL(url)
if (result == null || result == js.undefined) {
None
} else {
val json = Json.parse(js.JSON.stringify(result))
Some(json)
}
}

def appendURLProtocol(url: String): String = {
JSNLWExtensionsLoader.appendURLProtocol(url)
}

def validateURL(url: String): Boolean = {
JSNLWExtensionsLoader.validateURL(url)
}
}
22 changes: 22 additions & 0 deletions compiler/jvm/src/main/scala/NLWExtensionsLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This file is related to the NetLogo Web (NLW) project
// Please see org:NetLogo/Galapagos/app/assets/javascript/beak/nlw-extensions-loader.js
// for more details. This change was introduced in https://github.com/NetLogo/NetLogo/pull/2508
// This is not supported in the JVM compiler.
// Omar Ibrahim, 2025
package org.nlogo.tortoise.compiler
import scala.annotation.unused

import play.api.libs.json.JsValue


object NLWExtensionsLoader {
def getPrimitivesFromURL(@unused url: String): Option[JsValue] = {
throw new UnsupportedOperationException("NLWExtensionsLoader is not implemented in the JVM environment.")
}
def appendURLProtocol(@unused url: String): String = {
throw new UnsupportedOperationException("NLWExtensionsLoader is not implemented in the JVM environment.")
}
def validateURL(@unused url: String): Boolean = {
throw new UnsupportedOperationException("NLWExtensionsLoader is not implemented in the JVM environment.")
}
}
25 changes: 23 additions & 2 deletions compiler/shared/src/main/scala/NLWExtensionManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ object CreateExtension {

def apply(json: String): Extension = {
val jsExt = Json.parse(json)
fromJSON(jsExt)
}

def fromJSON(jsExt: JsValue): Extension = {
new Extension {
override def getName: String = {
jsExt("name").as[String]
Expand Down Expand Up @@ -195,11 +199,28 @@ class NLWExtensionManager extends ExtensionManager {
override def finishFullCompilation(): Unit = ()

override def importExtension(extName: String, errors: ErrorSource): Unit = {
val extension = extNameToExtMap.getOrElse(extName, failCompilation(s"No such extension: ${extName}"))
importExtension(extName, None, errors)
}
override def importExtension(extName: String, extURL: Option[String], errors: ErrorSource): Unit = {
val extension = extURL match {
case Some(url) =>
if (!NLWExtensionsLoader.validateURL(url)) {
failCompilation(s"Invalid URL for extension: ${url}")
}
val extObj = NLWExtensionsLoader.getPrimitivesFromURL(url)
extObj match {
case Some(obj) => CreateExtension.fromJSON(obj)
case None => failCompilation(s"Could not load extension from URL: ${url}")
}
case None => extNameToExtMap.getOrElse(extName, failCompilation(s"No such extension: ${extName}"))
}
val extPrimPairs = extension.getPrims.map(prim => (extension.getName, prim))
val shoutedPairs = extPrimPairs.map { case (extName, ExtensionPrim(prim, name)) => (s"$extName:$name".toUpperCase, prim) }
primNameToPrimMap ++= shoutedPairs
importedExtensions.add(extName)
importedExtensions.add(extURL match {
case Some(url) => NLWExtensionsLoader.appendURLProtocol(url)
case None => extName
})
()
}

Expand Down
17 changes: 17 additions & 0 deletions engine/src/main/coffee/extensions/all.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ module.exports = {
if upperNames.includes(upperName)
extensions[upperName] = extension
)
importedExtensions.filter(NLWExtensionsLoader.isURL).forEach( (url) ->
extensionModule = NLWExtensionsLoader.getExtensionModuleFromURL(url)
if extensionModule?
extension = extensionModule.init(workspace)
upperName = extension.name.toUpperCase()
extensions[upperName] = extension
else
console.warn "Extension at URL #{url} does not have an init function."
)
extensions

porters: (importedExtensions...) ->
Expand All @@ -26,6 +35,14 @@ module.exports = {
if upperNames.includes(upperName)
porters.push(extensionModule.porter)
)
importedExtensions.filter(NLWExtensionsLoader.isURL).forEach( (url) ->
extensionModule = NLWExtensionsLoader.getExtensionModuleFromURL(url)
if extensionModule? and extensionModule.porter?
upperName = extensionModule.porter.extensionName.toUpperCase()
porters.push(extensionModule.porter)
else
console.warn "Extension at URL #{url} does not have an init function."
)
porters

}
Loading