mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
262 lines
7.7 KiB
Markdown
262 lines
7.7 KiB
Markdown
|
|
So we need some syntax to distinguish between a relative import and a logical
|
||
|
|
import. I'm not sure which way to go, and I'd like some feedback (or possibly
|
||
|
|
other alternate ideas I haven't considered).
|
||
|
|
|
||
|
|
My two favorites are:
|
||
|
|
|
||
|
|
```
|
||
|
|
// Use
|
||
|
|
use "relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
|
||
|
|
// Node-style
|
||
|
|
import "./relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
If you folks are OK with "use", that's my preference. But otherwise, the Node
|
||
|
|
style will definitely work too. I'm open to other ideas as well, including a few
|
||
|
|
below, but I'd like to not bikeshed this forever.
|
||
|
|
|
||
|
|
## Background
|
||
|
|
|
||
|
|
There are four general approaches we can take:
|
||
|
|
|
||
|
|
### Use a modifier ("modifier")
|
||
|
|
|
||
|
|
Both kinds of imports start with `import`, but then we use a second keyword
|
||
|
|
afterwards to identify either a relative or logical import. We could use *two*
|
||
|
|
keywords -- one for each kind -- but that's unnecessarily verbose. Instead, we
|
||
|
|
use the presence or absence of the keyword to distinguish. In other words:
|
||
|
|
|
||
|
|
```
|
||
|
|
import foo "string"
|
||
|
|
import "string"
|
||
|
|
```
|
||
|
|
|
||
|
|
The specific questions we have to answer are:
|
||
|
|
|
||
|
|
1. Which kind of import gets the keyword? Ideally, the most common kind of
|
||
|
|
import would be the one that doesn't need an extra keyword.
|
||
|
|
|
||
|
|
2. What keyword? This is surprisingly hard. Probably some kind of preposition.
|
||
|
|
|
||
|
|
### Use different keywords ("keyword")
|
||
|
|
|
||
|
|
Instead of using `import` for both logical and relative imports, we could have
|
||
|
|
two keywords, one for each kind. The specific questions to answer then are:
|
||
|
|
|
||
|
|
1. Which kind of import gets `import`?
|
||
|
|
2. What's the other keyword?
|
||
|
|
|
||
|
|
### Use different syntax for the path ("syntax")
|
||
|
|
|
||
|
|
Instead of always using a string literal to identify what's being imported, we
|
||
|
|
could use a different kind of token or tokens for the different kinds of import.
|
||
|
|
For example, a string literal for one kind, and an identifier token for the
|
||
|
|
other:
|
||
|
|
|
||
|
|
import identifier
|
||
|
|
import "string literal"
|
||
|
|
|
||
|
|
The specific questions are:
|
||
|
|
|
||
|
|
1. Which kind of import uses a string literal?
|
||
|
|
2. What's the syntax for the other kind?
|
||
|
|
|
||
|
|
### Use a signifier in the import string itself to distinguish ("string")
|
||
|
|
|
||
|
|
An import is always `import` followed by a string literal. Then we use some
|
||
|
|
specific markers inside the string literal itself to distinguish the two kinds.
|
||
|
|
For example, Node says that an import string starting with "./" or "../" is
|
||
|
|
relative and other import strings are logical.
|
||
|
|
|
||
|
|
The specific question to answer is what kind of signifier we'd use. I think
|
||
|
|
Node's convention is the only real contender here, though.
|
||
|
|
|
||
|
|
One feature this style has that none of the others do is that it means the
|
||
|
|
language syntax itself has no notion of logical and relative imports. This
|
||
|
|
means there is no overhead or complexity for host applications where that
|
||
|
|
distinction isn't meaningful.
|
||
|
|
|
||
|
|
## Contenders
|
||
|
|
|
||
|
|
These are options I'm open to, in roughly descending order of preference:
|
||
|
|
|
||
|
|
### Node-style (string)
|
||
|
|
|
||
|
|
If the string starts with "./" or "../", it's relative.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "./relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
This is how Node works, so there's prior art. It keeps the language completely
|
||
|
|
simple. It does feel sort of arbitrary and magical to me, but it's the simplest,
|
||
|
|
most expedient solution.
|
||
|
|
|
||
|
|
### Use (keyword)
|
||
|
|
|
||
|
|
The `use` keyword is for relative imports, `import` is for logical.
|
||
|
|
|
||
|
|
```
|
||
|
|
use "relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
The `use` keyword comes from Pascal, but that's not very widely known. I kind
|
||
|
|
of like this. It's short, and `use` feels "nearer" to me than "import" so it
|
||
|
|
has the right connotation. (You can't "use" something unless you have it near
|
||
|
|
to hand.)
|
||
|
|
|
||
|
|
It adds a little complexity to the language and VM. We have to support both
|
||
|
|
keywords and pass that "use versus import" bit through the name resolution
|
||
|
|
process. But that's pretty minor.
|
||
|
|
|
||
|
|
### Slashes (syntax)
|
||
|
|
|
||
|
|
If the path is a string literal, it's relative. Otherwise, it is a
|
||
|
|
slash-separated series of unquoted identifiers.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import logical/path
|
||
|
|
```
|
||
|
|
|
||
|
|
This means you can't (easily) use reserved words as names of logical imports.
|
||
|
|
This was my initial pitch. I still like how it looks, but I seem to be in the
|
||
|
|
minority.
|
||
|
|
|
||
|
|
### Relative (modifier)
|
||
|
|
|
||
|
|
The `relative` modifier is for relative imports.
|
||
|
|
|
||
|
|
```
|
||
|
|
import relative "relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
It's explicit, which is good. It is unfortunately verbose. I think `relative`
|
||
|
|
is too useful of a word to make into a reserved word, which means it would have
|
||
|
|
to be a contextual keyword (i.e. treated like a reserved word after `import`
|
||
|
|
but behaving like a regular identifier elsewhere). I'm not generally a fan of
|
||
|
|
contextual keywords—they tend to make things like syntax highlighters more
|
||
|
|
difficult to create—so I try to avoid them.
|
||
|
|
|
||
|
|
## Rejected
|
||
|
|
|
||
|
|
I considered these ideas, but don't think they are good enough approaches for
|
||
|
|
various reasons:
|
||
|
|
|
||
|
|
### Package identifier (syntax)
|
||
|
|
|
||
|
|
If an unquoted identifier appears before the import string, then it's a logical
|
||
|
|
import within that package. Otherwise, it's relative.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import logical "path"
|
||
|
|
```
|
||
|
|
|
||
|
|
This was one of my initial ideas. It has the same problem as other unquoted
|
||
|
|
imports in that it makes it harder to have odd package names. It means the VM
|
||
|
|
has to understand this syntax and figure out how to display package names in
|
||
|
|
stack traces and stuff, so there is some extra complexity involved.
|
||
|
|
|
||
|
|
The form where you have both a package name and a relative path within that
|
||
|
|
package is pretty unusual and likely unintuitive to users.
|
||
|
|
|
||
|
|
### Dotted (syntax)
|
||
|
|
|
||
|
|
If the path is a string literal, it's relative. Otherwise, it is a
|
||
|
|
dot-separated series of unquoted identifiers.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import logical.path
|
||
|
|
```
|
||
|
|
|
||
|
|
Similar to slashes, but using dots. This helps make logical imports look more
|
||
|
|
visually distinct from relative ones. But it also makes them look more similar
|
||
|
|
to getter calls, which they aren't related to at all.
|
||
|
|
|
||
|
|
### Include (keyword)
|
||
|
|
|
||
|
|
The `include` keyword is for relative imports, `import` is for logical.
|
||
|
|
|
||
|
|
```
|
||
|
|
include "relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
Ruby uses `include` for applying mixins. "Include" reads to me more like some
|
||
|
|
kind of transclusion thing, so it feels a little weird.
|
||
|
|
|
||
|
|
### Require (keyword)
|
||
|
|
|
||
|
|
The `require` keyword is for relative imports, `import` is for logical.
|
||
|
|
|
||
|
|
```
|
||
|
|
require "relative/path"
|
||
|
|
import "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
Node uses "require" and ES6 uses "import" so this is kind of confusing. Ruby
|
||
|
|
uses `require` and `require_relative`, so using `require` for a relative import
|
||
|
|
is kind of confusing. Lua also uses `require`, but for both relative and
|
||
|
|
logical. Overall, this feels murky and unhelpful to me.
|
||
|
|
|
||
|
|
### Angle-brackets (syntax)
|
||
|
|
|
||
|
|
As in C/C++, an import string can be in angle brackets or quotes. Angle brackets
|
||
|
|
are for logical imports, quotes for relative.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import <logical/path>
|
||
|
|
```
|
||
|
|
|
||
|
|
Hard pass. It requires context-sensitive tokenization (!) in C and we definitely
|
||
|
|
don't want to go there.
|
||
|
|
|
||
|
|
### URI scheme (string)
|
||
|
|
|
||
|
|
An import string starting with "package:" and maybe "wren:" is treated as
|
||
|
|
logical, like they are URIs with an explicit scheme. Others are relative.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import "package:logical/path"
|
||
|
|
import "wren:random"
|
||
|
|
```
|
||
|
|
|
||
|
|
This is (roughly) how Dart works. I'm not a fan. I think it's too verbose for
|
||
|
|
logical imports.
|
||
|
|
|
||
|
|
### Package (modifier)
|
||
|
|
|
||
|
|
A `package` modifier indicates a logical import. Others are relative.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "relative/path"
|
||
|
|
import package "logical/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
Pretty long, and I'm not too crazy about baking "package" into the language and
|
||
|
|
VM.
|
||
|
|
|
||
|
|
### From (modifier)
|
||
|
|
|
||
|
|
A `from` modifier indicates, uh, one kind of import.
|
||
|
|
|
||
|
|
```
|
||
|
|
import "some/path"
|
||
|
|
import from "other/path"
|
||
|
|
```
|
||
|
|
|
||
|
|
It looks nice, but it's totally unclear to me whether logical imports should
|
||
|
|
get `from` or relative ones. Also kind of confusing in that Python and ES6 use
|
||
|
|
`from` in their notation for importing explicit variables from a module (where
|
||
|
|
Wren uses `for`).
|