Skip to content

lue-bird/elm-syntax-to-moonbit

Repository files navigation

Print elm-syntax declarations as moonbit code. To try it out, you can run this script.

import Elm.Parser
import ElmSyntaxToMoonbit

"""module Sample exposing (..)

plus2 : Float -> Float
plus2 n =
    n + ([ 2.0 ] |> List.sum)
"""
    |> Elm.Parser.parseToFile
    |> Result.mapError (\_ -> "failed to parse elm source code")
    |> Result.map
        (\syntaxModule ->
            [ syntaxModule ]
                |> ElmSyntaxToMoonbit.modules
                |> .declarations
                |> ElmSyntaxToMoonbit.moonbitDeclarationsToModuleString
        )
-->
Ok """...
pub fn sample_plus2(n: Double) -> Double {
    basics_add(n, list_sum_float(@list.of([2.0])))
}
"""

be aware

  • not supported are
    • ports that use non-json values like port sendMessage : String -> Cmd msg, glsl, phantom types, == on a generic value
    • elm/file, elm/http, elm/browser, elm-explorations/markdown, elm-explorations/webgl, elm-explorations/benchmark, elm/regex (because no support in moonbitlang/core), elm-explorations/linear-algebra (because no support in moonbitlang/core)
    • Task, Process, Platform.Task, Platform.ProcessId, Platform.Router, Platform.sendToApp, Platform.sendToSelf, Random.generate, Time.now, Time.every, Time.here, Time.getZoneName, Bytes.getHostEndianness
    • extensible record types outside of module-level value/function declarations. For example, these declarations might not work:
      -- in variant value
      type Named rec = Named { rec | name : String }
      -- in let type, annotated or not
      let getName : { r | name : name } -> name
      Allowed is only record extension in module-level value/functions, annotated or not:
      userId : { u | name : String, server : Domain } -> String
      In the non-allowed cases listed above, we assume that you intended to use a regular record type with only the extension fields which can lead to moonbit compile errors if you actually pass in additional fields.
    • elm's Char.toLocale[Case] functions will just behave like Char.to[Case]
    • elm's VirtualDom/Html/Svg.lazyN functions will still exist for compatibility but they will behave just like constructing them eagerly
  • dependencies cannot internally use the same module names as the transpiled project
  • the resulting code might not be readable or even conventionally formatted and comments are not preserved

Please report any issues you notice <3

why moonbit?

  • it has first-class support for wasm (and native)
  • it feels like a superset of elm which makes transpiling and "ffi" easier
  • it supposedly has fast compile times (I have yet to verify this claim)

why not moonbit?

  • the language is bloated
  • the language is very young and tooling like the build CLI are fragile
  • the language's promotion has made overly big claims and has the classic AI bullshit
  • the ecosystem is tiny so you will likely need to write your own FFI wrappers and similar

how do I use the transpiled output?

An example can be found in example-hello-world/.

In your elm project, add moon.pkg

options("is-main": true, "warn-list": "-1-6-71")

and moon.mod.json

{"name": "your_project_name"}

(If you know of a simpler setup, please open an issue)

and a file main.mbt that uses elm.mbt:

mod elm
print(your_module_your_function("yourInput"))

where your_module_your_function(firstArgument, secondArgument) is the transpiled elm function Your.Module.yourFunction firstArgument secondArgument. (If the value/function contains extensible records, search for your_module_your_function_ with the underscore to see the different specialized options)

Run with

moon run .

If something unexpected happened, please report an issue.

In the transpiled code, you will find these types:

  • elm Bool (True or False) → moonbit Bool (true or false), Char ('a') → Char ('a'), ( Bool, Char )( Bool, Char )
  • elm Ints will be of type Int64. Create and match by appending L to any number literal or using Int::to_int64/Int64::from_int
  • elm Floats will be of type f64. Create and match by using any number literal with a decimal point
  • elm Strings (like "a") will be of the custom type StringString. Create from literals or other string slices with (StringString::One("a")). Match with your_string if string_string_string_equals_stringing(your_string, "some string")
  • elm records like { y : Float, x : Float } will be of type GeneratedXY<f64, f64> with the fields sorted and can be constructed and matched with { x: _, y: _ }. record.x access also works
  • elm Lists will be of type @list.List. Create and match with the help of @list/List::
  • elm Arrays will be of type @immut/array.Array. Create and match with the helpers in @immut/array
  • elm Sets will be of type @immut/sorted_set.SortedSet. Create and match with the helpers in @immut/sorted_set
  • elm Dicts will be of type @immut/sorted_map.SortedMap. Create and match with the helpers in @immut/sorted_map
  • elm Json.Encode.Value/Json.Decode.Values will be of type Json. Create and match with the help of @json./Json::
  • elm Bytes.Bytes will be of type Bytes. Create and match with the help of @bytes/Bytes::
  • a transpiled elm app does not run itself. An elm main Platform.worker program type will literally just consist of fields init, update and subscriptions where subscriptions/commands are returned as a list of PlatformSubSingle/PlatformCmdSingle with possible elm subscriptions/commands in a choice type. It's then your responsibility as "the platform" to perform effects, create events and manage the state. For an example see example-worker-blocking/ & example-worker-concurrent/

improvement ideas

  • try and benchmark switching String representation from One &str | Append String String to (StringBuilder) -> StringBuilder
  • if lambda is called with a function, always inline that function
  • your idea 👀

About

transpile elm to moonbit

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages