Sign in

    gleam-parser-morphir

    Optional Morphir target profile for moonrockz/gleam-parser

    gleam
    parser
    morphir
    Download zip
    Author
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    4 days ago
    Downloads
    19

    #Morphir target profile

    See the README with tested examples for installation, configuration, external functions and types, composition, and releases.

    Release history is in CHANGELOG.md.

    #Morphir target profile for gleam-parser

    This optional MoonBit module adds the morphir target to the core parser. It has its own release identity, moonrockz/gleam-parser-morphir@0.1.2, and requires core parser 0.5.0. Installing the core does not install this module.

    Install it with moon add moonrockz/gleam-parser-morphir. In moon.pkg, import:

    import { "moonrockz/gleam-parser-morphir" @morphir, }

    The profile re-exports the common core APIs used below. These functions keep standard Gleam as their default. If you need other core APIs, also run moon add moonrockz/gleam-parser@0.5.0 and import that package as @gleam.

    #What Gleam does this profile accept?

    The profile extends the standard Gleam 1.19 preset with the target name morphir. It uses the existing @target and @external annotation syntax. Ordinary Gleam expressions, types, and functions keep their existing rules. The examples below show the Gleam source first, then the MoonBit parser call.

    SourceStandard presetWith the Morphir profile
    Ordinary Gleam and standard target annotationsAcceptedAccepted
    @target(morphir)Unknown targetDefinition retains a Morphir restriction
    @external(morphir, "module", "symbol")Unknown targetFunction or external type retains a Morphir binding
    @target(scala) or @target(go)Unknown targetUnknown target unless another profile registers it

    These are parser extensions. The standard Gleam compiler does not accept the Morphir annotations. The profile supplies no Morphir code generator. External module and symbol strings below are illustrative references; the parser does not check that a backend implements them.

    #Mark a definition for Morphir

    A downstream translator can use this restriction to select a definition. The parser retains both definitions in this file:

    @target(morphir) pub const destination = "morphir" pub const shared = 42

    ///|
    test "mr: retain restricted and shared definitions" {
    let source =
    #|@target(morphir)
    #|pub const destination = "morphir"
    #|
    #|pub const shared = 42
    let config = {
    ..@morphir.Config::default(),
    dialect: @morphir.Dialect::gleam_1_19().with_profile(@morphir.profile()),
    }
    assert_eq(@morphir.parse_text(source).has_errors(), true)
    let result = @morphir.parse_text(source, config~)
    assert_eq(result.has_errors(), false)
    assert_eq(result.module_.definitions.length(), 2)
    assert_eq(
    result.module_.definitions[0].target.map(t => t.name()),
    Some("morphir"),
    )
    assert_eq(result.module_.definitions[1].target, None)
    }

    #Parse a Morphir external function

    Enable the profile explicitly. @external retains the target, module, symbol, and source location. It does not restrict the definition to Morphir. Standard and Morphir bindings can coexist on the same function:

    @external(erlang, "erlang", "length") @external(javascript, "./list.mjs", "length") @external(morphir, "morphir/sdk/list", "length") pub fn length(xs: List(a)) -> Int

    ///|
    test "mr: retain a Morphir function binding" {
    let dialect = @morphir.Dialect::gleam_1_19().with_profile(@morphir.profile())
    let config = { ..@morphir.Config::default(), dialect, }
    let source =
    #|@external(erlang, "erlang", "length")
    #|@external(javascript, "./list.mjs", "length")
    #|@external(morphir, "morphir/sdk/list", "length")
    #|pub fn length(xs: List(a)) -> Int
    #|
    let result = @morphir.parse_text(source, config~)
    assert_eq(result.has_errors(), false)
    guard result.module_.definitions[0].definition is Function(function) else {
    fail("expected a function")
    }
    assert_eq(function.externals.all().length(), 3)
    assert_eq(
    function.external_for(@morphir.Target::Erlang).unwrap().module_,
    "erlang",
    )
    assert_eq(
    function.external_for(@morphir.Target::JavaScript).unwrap().module_,
    "./list.mjs",
    )
    let target = dialect.targets.resolve("morphir").unwrap()
    let binding = function.external_for(target).unwrap()
    assert_eq(binding.module_, "morphir/sdk/list")
    assert_eq(binding.function, "length")
    assert_eq(result.module_.definitions[0].target, None)
    assert_eq(@morphir.format_parsed(result), source)
    assert_eq(
    @morphir.decode_extended(@morphir.encode_extended(result)).parsed,
    result.parsed(),
    )
    }

    #Retain a target restriction and an external type

    Use @target(morphir) for a definition's target restriction. Parsing retains all definitions. A consumer decides which target to translate later. Here the type has both a restriction and an external binding:

    @target(morphir) @external(morphir, "example/domain", "CustomerId") pub type CustomerId

    Remove @target(morphir) to keep the binding without restricting the type. The external annotation supplies the type's foreign name; it does not define constructors or validate that the foreign type exists.

    ///|
    test "mr: external types and restrictions stay distinct" {
    let dialect = @morphir.Dialect::gleam_1_19().with_profile(@morphir.profile())
    let config = { ..@morphir.Config::default(), dialect, }
    let result = @morphir.parse_text(
    "@target(morphir)\n@external(morphir, \"example/domain\", \"CustomerId\")\npub type CustomerId\n",
    config~,
    )
    assert_eq(result.has_errors(), false)
    assert_eq(
    result.module_.definitions[0].target.map(t => t.name()),
    Some("morphir"),
    )
    guard result.module_.definitions[0].definition is CustomType(type_) else {
    fail("expected an external type")
    }
    assert_eq(type_.externals.all()[0].external.function, "CustomerId")
    assert_eq(
    @morphir.parse_text("@target(morphir) pub const x = 1").has_errors(),
    true,
    )
    assert_eq(dialect.knows_target("go"), false)
    }

    #Compose with another provider

    Other modules use the same public APIs. Conflicting registrations raise ProfileError; no registration silently overrides another.

    ///|
    test "mr: compose a separately defined target" {
    let scala = @morphir.Profile(
    "example/scala@1",
    [@morphir.Target::extension("scala")][:],
    )
    let dialect = @morphir.Dialect::gleam_1_19()
    .with_profile(@morphir.profile())
    .with_profile(scala)
    assert_eq(dialect.knows_target("morphir"), true)
    assert_eq(dialect.knows_target("scala"), true)
    let available = @morphir.Profiles([@morphir.profile(), scala][:])
    let config = @morphir.Config::from_toml(
    "[parser]\nprofiles = [\"moonrockz/gleam-parser-morphir@0.1.2\", \"example/scala@1\"]\n",
    profiles=available,
    ).to_config(@morphir.Config::default())
    assert_eq(config.dialect, dialect)
    }

    This module supplies no Morphir backend, symbol resolution, or foreign-call execution. Module and symbol strings are opaque. Use extended JSON to retain Morphir metadata. Compiler-compatible export raises NonStandardTarget. See the core target profile cookbook and migration guide.

    #Release this module

    Publish the required core version first. Run the Release Morphir profile workflow on the commit to release. Verify the module version on mooncakes.io, then create the morphir-v0.1.2 GitHub release at the same commit. Future profile releases update this module's manifest, version(), profile identity, and notes independently of the core version.

    Config

    Configuration: Config (dialect, fuel) from defaults, a TOML text, the environment and the caller, merged with with_fallback; Config::load reads gleam-parser.toml and GLEAM_PARSER_*.

    Dialect

    Dialects: what the parser accepts (Dialect::gleam_1_19() is the default) and the deprecation Rules it warns about.

    Profile

    Named optional registrations and the application's available-profile registry.

    Profiles

    Named optional registrations and the application's available-profile registry.

    Target

    Standard and validated extension target identities.

    decode_extended

    Decode extended JSON with its embedded registry. No optional module is loaded. Reject unsupported versions, invalid registrations, unknown target references, and duplicate bindings with a source path in DecodeError.

    decode_parsed

    Decode a module with its trivia positions and parse-time warnings. Raise DecodeError with the field path for malformed JSON values.

    encode_extended

    Encode a parse result in the versioned extended JSON format. Record the dialect, profile identities, and registry used to parse the source. This preserves extension targets and bindings that compiler-compatible export rejects.

    test {
    let orbit = @gleam.Target::extension("orbit")
    let config = {
    ..@gleam.Config::default(),
    dialect: @gleam.Dialect::gleam_1_19().register_target(orbit),
    }
    let result = @gleam.parse_text("@target(orbit) const answer = 42", config~)
    let document = @gleam.decode_extended(@gleam.encode_extended(result))
    assert_eq(document.parsed, result.parsed())
    }

    encode_parsed

    Encode a module with its trivia positions and parse-time warnings. Raise ExportError for extension targets; use encode_extended to retain them. Use canonical to write the JSON in the oracle's key order.

    test {
    let parsed = @gleam.parse_text("const x = 1").parsed()
    assert_eq(@gleam.decode_parsed(@gleam.encode_parsed(parsed)), parsed)
    }

    format

    Format Gleam source as gleam format does (see @format.format): 80 columns, two spaces of indentation, comments and empty lines kept. Raises ParseFailed with the error diagnostics when the text has a syntax error.

    test {
    inspect(@gleam.format("const x = 1\n"), content="const x = 1\n")
    }

    format_parsed

    Format a parse result (see @format.format_parsed).

    parse_text

    Parse Gleam source text into a module, with recovery per definition. config sets the dialect and the fuel budget; see Config.

    test {
    let r = @gleam.parse_text("pub fn main() {\n io.println(\"hi\")\n}\n")
    inspect(r.module_.definitions.length(), content="1")
    inspect(r.has_errors(), content="false")
    }

    profile

    Construct the optional Morphir profile through the public core API. It registers only morphir. Standard targets stay available; other extension targets stay unregistered. Importing this module does not enable the profile. Pass it to Dialect::with_profile explicitly. The registration supplies syntax and AST metadata, not a Morphir backend. Module and symbol strings are opaque references interpreted by consumers. It accepts these Gleam annotations, which the standard preset rejects:

    @target(morphir) pub const answer = 42 @external(morphir, "example/domain", "CustomerId") pub type CustomerId

    @target records a restriction. @external records a binding without adding a restriction. Standard Erlang and JavaScript bindings can coexist with a Morphir binding on the same function or external type.

    test {
    let dialect = @morphir.Dialect::gleam_1_19().with_profile(@morphir.profile())
    let config = { ..@morphir.Config::default(), dialect, }
    let result = @morphir.parse_text(
    "@target(morphir) pub const answer = 42",
    config~,
    )
    assert_eq(result.has_errors(), false)
    assert_eq(dialect.knows_target("scala"), false)
    }

    version

    fn version() -> String

    Return the Morphir profile's release version. It is independent of the core parser version. This module requires core parser 0.5.0 or later.

    Source Files

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io