Glimpse

A library for parsing, loading, and typechecking a Gleam project. It wraps the AST produced by glance with two pieces:

Glimpse is not filesystem aware: all modules are loaded externally through a loader function. It provides tooling to determine which dependencies need to be loaded, and it typechecks across module boundaries.

It is not yet a complete Gleam typechecker, but it covers the common parts of the language so that folks targeting different languages from Gleam can focus on codegen.

Glimpse 0.9.0 is available on hex.pm.

Docs: https://hexdocs.pm/glimpse/ Repo: https://github.com/dusty-phillips/glimpse

Install

gleam add glimpse

Quickstart

Load a package, then typecheck it for the Erlang target:

import gleam/io
import gleam/result
import glimpse
import glimpse/target
import glimpse/typecheck

fn load_module(module_name: String) -> Result(String, Nil) {
  // read the module contents from the filesystem here
  Ok("<contents of " <> module_name <> ".gleam>")
}

pub fn main() {
  let package =
    glimpse.load_package("my_package", load_module)
    |> result.map(typecheck.package(_, target.Erlang))

  case package {
    Ok(_) -> io.println("typechecked ok")
    Error(_) -> io.println("typecheck failed")
  }
}

The package is loaded with a loader function (see below) and returned with inferred types filled in. You can then iterate the modules and inspect the resolved AST.

Loading packages

The main entry point is glimpse.load_package. It accepts the name of the package and a function that accepts the string name of a module and returns the contents of the module. The loader is called with the main module for the package (which is always the package name) and, recursively, for every module that is imported.

Here’s a condensed example from macabre, a Gleam-to-Python compiler:

fn load_glimpse_package(
  project: project.Project,
) -> Result(glimpse.Package, errors.Error) {
  glimpse.load_package(project.name, fn(module_name) {
    let path =
      filepath.join(project.build_src_dir(project), module_name <> ".gleam")
    filesystem.read(path)
  })
  |> result.map_error(fn(error) {
    case error {
      glimpse.LoadError(error) -> error
      glimpse.ParseError(glance_error, name, content) ->
        errors.GlanceParseError(glance_error, name, content)
    }
  })
}

Typechecking

Glimpse can typecheck a loaded package, both within and between modules. The entry point you’ll usually want is typecheck.package, which sorts the modules by their dependencies and checks each in turn. It takes the target the package is being built for, and returns the package with inferred types filled in:

pub fn package(
  package: glimpse.Package,
  target: target.Target,
) -> Result(glimpse.Package, error.GlimpseError(a))

Targets

Glimpse handles @target(erlang) / @target(javascript) annotations: definitions that are not active for the target being checked are filtered out before typechecking, mirroring the real compiler. Pass target.Erlang or target.Javascript to typecheck.package.

Experimental backends targeting another language can use target.Named(name) to check with their own target, so a @target(python) definition is active when checking for target.Named("python") and filtered out otherwise. The same matching applies to @external(...) annotations.

Lower-level entry points

glimpse/typecheck also exposes functions for checking a single module, constant, or function against an existing type environment:

Errors

Functions return a Result, with errors reported as glimpse/error’s GlimpseError type. Its variants cover the whole pipeline:

Because Glimpse is not filesystem-aware, it can’t discover which modules are dev-only on its own. Set Package.dev_dependencies to the names of those modules after loading so that a source module importing one is reported as an ImportError, mirroring the real compiler’s src/dev split.

Future Ideas

My vision for the project is that it handles the common parts of gleam compilation so that folks wanting to target different languages from gleam can focus on the codegen part.

Desugaring

I’d like to add some desugaring so that the output of glimpse is actually a simpler AST than the glance AST representing the full gleam language. This would reduce the footprint that compiler implementers need to cover while still targeting the entire language.

A couple ideas include:

If I tackle this, it will probably happen before type checking and inference so the typechecker also doesnt have to cover the entirety of glance.

Token positions

The glance library does not maintain token positions with AST nodes. It does record the locations of parse errors, but that doesn’t help with locating errors further up the chain. E.g. when there is an error with typechecking, there is currently no way to communicate to the user where the error occurred.

Solving this requires modifications to, a rewrite of, or a fork of the glance library. I’m not willing to tackle that anytime soon, but it is a prerequisite for the vision for this project.

Development

gleam test  # Run the tests
Search Document