jamiebuilds / ghost-lang

:ghost: A friendly little language for you and me.
302 stars 10 forks source link

Importing #37

Open jamiebuilds opened 5 years ago

jamiebuilds commented 5 years ago

Specifiers

Standard Library Packages

The import bare syntax is reserved for packages in the standard library.

import time
import math

Community Packages

Packages outside of the standard library will always be scoped.

import facebook/react
import jamiebuilds/unstated

Local Packages

Packages can also exist within a local project, and will follow the same scoping rules as community packages.

import mycompany/api
import mycompany/utils

Local Modules

import ./sibling
import ../parent
import ../../grandparent
import ../../path/to/cousin

Scoping rules

Bare Namespace

When importing a module with import ./mod the name mod becomes a namespace containing all of the modules exports

import ./mod
# mod.foo
# mod.bar
# mod.baz

Renamed Namespaces

You can rename namespaces using import ./mod as other.

import ./mod as other
# other.foo (mod.foo)
# other.bar (mod.bar)
# other.baz (mod.baz)

Renaming is useful to avoid clashes

Destructuring Exports

Alternatively, you can pick the exports you need without putting them on a namespace.

import ./mod as { foo, bar, baz }
# foo
# bar
# baz

Just like normal destructuring, you can also use ...rest to put any remaining exports onto a namespace.

import ./mod as { foo, ...rest }
# foo
# rest.bar
# rest.baz

Also like normal destructuring, you can also rename within destructuring.

import ./mod as { foo as bam, bar as bat }
# bam (foo)
# bat (bat)

Spread Exports

Inversely, you can spread all exports into the local scope.

import ./mod as ...
# foo
# bar
# baz

This can be extremely useful for DSLs where you want lots of things in scope:

import css as ...

let rule = css(
  display(.block),
  fontFamily(.sansSerif),
)

Default Exports

Modules can also have "default" exports by naming an export the same thing as the file name.

# foo.ghost:
let foo = fn () {} # because `foo` matches `foo.ghost` it gets special treatment (see below)
let bar = fn () {}
let baz = fn () {}

# other.ghost:
import ./foo                 # in scope: foo, foo.foo, foo.bar
import ./foo as bat          # in scope: bat (foo), bat.foo, bat.bar
import ./foo as { foo, bar } # in scope: foo, bar
import ./foo as { ...bat }   # in scope: bat.foo, bat.bar, bat.baz
import ./foo as ...          # in scope: foo, bar, baz

I want to have a concept of default exports because I don't want to end up with imports like this:

import ./foo
# foo.foo

import ./foo as { foo }
# foo

import ./bar # named "bar" just so you don't repeat "foo.foo"
# bar.foo

I think that it is okay to use the filename for this because I already consider the filename as part of the public API of a module.