Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add import system #544

Merged
merged 32 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ba2c66f
Add basic import support
LunaAmora Jun 5, 2024
9c483c4
Add diagnostic config to allow for missing main definitions
LunaAmora Jun 5, 2024
9f9b8d7
Fix typo
LunaAmora Jun 5, 2024
d17ccc0
Make imports relative to the file it is declared
LunaAmora Jun 7, 2024
7c3c919
Limit adt tag defs be generated or builtin
LunaAmora Jun 7, 2024
1d4a2a5
Check for import name collision, fix import shadowing
LunaAmora Jun 7, 2024
dedfe42
Add glob imports
LunaAmora Jun 7, 2024
234ebdb
Add import aliases
LunaAmora Jun 10, 2024
3ac966c
Improve errors on conflits of imported names
LunaAmora Jun 10, 2024
84033a7
Fix imported names conflict error messages
LunaAmora Jun 10, 2024
9073c6d
Fix add_imported_adt error reporting
LunaAmora Jun 10, 2024
92c75d7
Fix backticks in imports error messages
LunaAmora Jun 10, 2024
d1072da
Fix import order dependent bugs
LunaAmora Jun 11, 2024
7ae5886
Add some imports tests
LunaAmora Jun 11, 2024
ddc987f
Fix relative imports in same folder
LunaAmora Jun 11, 2024
a769cc7
Add Import of full package namespace, aliased to 'mod/name'
LunaAmora Jun 11, 2024
5666077
Update import syntax to be python-like
LunaAmora Jun 19, 2024
3b91b0b
Normalize import paths, update absolute path folder
LunaAmora Jun 19, 2024
2cc9e97
Fix 'free_vars' to be deterministic
LunaAmora Jun 19, 2024
74e53eb
Split import into multiple files
LunaAmora Jun 19, 2024
66796e8
Add more import system documentation
LunaAmora Jun 20, 2024
21fe38e
Add support for hvm_defs in the import system
LunaAmora Jun 20, 2024
b7f5b9f
Add PackageLoader documentation, rename some enums
LunaAmora Jun 20, 2024
050f67a
Add import system .md docs, add to changelog
LunaAmora Jun 20, 2024
4814742
Improve imports when a path is both a file and a directory
LunaAmora Jun 24, 2024
53ca868
Fix entry point reimports to give an error
LunaAmora Jun 24, 2024
8e289f6
Fix apply_imports order
LunaAmora Jul 2, 2024
7101954
Improve import system functions, fix bugs
LunaAmora Jul 3, 2024
dbf4830
Improve import system docs with a project exaple
LunaAmora Jul 3, 2024
08500f2
Add import system docs for types and same name defs
LunaAmora Jul 4, 2024
918d7e6
Update Changelog
LunaAmora Jul 4, 2024
cd2a58b
Fix typo in imports.md
LunaAmora Jul 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project does not currently adhere to a particular versioning scheme.
### Fixed

- Fix variable binding in pattern matching when the irrefutable pattern optimization occurs. ([#618][gh-618])
- Don't warn on unused generated definitions. ([#514][gh-514])

### Added

- Add import system ([#544][gh-544])

## [0.2.36] - 2024-07-04

Expand Down Expand Up @@ -379,3 +384,6 @@ and this project does not currently adhere to a particular versioning scheme.
[gh-586]: https://github.com/HigherOrderCO/Bend/issues/586
[gh-596]: https://github.com/HigherOrderCO/Bend/issues/596
[gh-618]: https://github.com/HigherOrderCO/Bend/issues/618
[gh-514]: https://github.com/HigherOrderCO/Bend/issues/514
[gh-544]: https://github.com/HigherOrderCO/Bend/pull/544
[Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD
181 changes: 181 additions & 0 deletions docs/imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Import System

## Case Sensitivity
All import paths are case-sensitive. Ensure that the case used in import statements matches exactly with the file and directory names.

## Syntax
Imports can be declared two ways:

```py
from path import name
# or
import path/name
```

## Project Structure
Let's assume we have a bend project with the following structure:

```
my_project/
├── main.bend
├── utils/
│ ├── helper.bend
│ │ └── def calc
│ └── math.bend
│ ├── def add
│ └── def subtract
```

## Importing Relative Paths
Paths starting with `./` or `../` are imported relative to the file.

### Example:
```py
# if used inside `my_project/*.bend`
from ./utils import helper
# if used inside `my_project/*/*.bend`
import ../utils/helper
```

This will bind `calc` from `helper.bend` as `helper/calc`.

## Importing Absolute Paths
Otherwise, paths imported are relative to the folder of the main file.

### Example:
```py
from utils import math
# or
import utils/math
```

This will bind `add` and `subtract` from `math.bend` as `math/add` and `math/subtract`.

## Importing Specific Top-Level Names
You can import specific top-level names from a file.

### Example:
```py
from utils/helper import calc
from utils/math import (add, subtract)
# or
import (utils/helper/calc, utils/math/add, utils/math/subtract)
# or
import utils/helper/calc
import utils/math/add
import utils/math/subtract
```

This will bind the names `calc`, `add` and `subtract` from their respective files.

## Importing All Names
You can import all top-level names from a file using the wildcard `*`.

### Example:
```py
from utils/math import *
```

This will bind the names `add` and `subtract` from `math.bend`.

## Importing All `.bend` Files from a Folder
You can import all `.bend` files from a folder using the wildcard `*`.

### Example:
```py
from utils import *
```

This will bind the names from `helper.bend` and `math.bend`, as `helper/calc`, `math/add` and `math/subtract`.

## Aliasing Imports
You can alias imports to a different name for convenience.

### Importing a File with Alias
Import the `file` top-level name from `file.bend` aliased to `alias`, and all other names as `alias/name`.

### Example:
```py
from utils import helper as utilsHelper
import utils/math as mathLib
```

This will bind the names from `helper.bend` and `math.bend`, as `utilsHelper/calc`, `mathLib/add` and `mathLib/subtract`.

### Importing Specific Names with Aliases
You can import specific top-level names and alias them to different names.

### Example:
```py
from utils/helper import calc as calcFunc
from utils/math import (add as addFunc, subtract as subFunc)
# or
import (utils/math/add as addFunc, utils/math/subtract as subFunc)
```

This will bind `calc`, `add` and `subtract` as `calcFunc`, `addFunc` and `subFunc` from their respective files.

## Project Structure
Let's assume we have a bend project with the following structure:

```
my_project/
├── main.bend
├── types/
│ ├── List.bend
│ │ └── type List: Nil | (Cons ..)
│ └── List/
│ ├── concat.bend
│ │ └── def concat
│ └── append.bend
│ └── def append
│ └── def helper
```

## Importing data types

You can import a data type and its constructors by only importing its name.

### Example:
```py
from types/List import List
# behaves the same as
from types/List import (List, List/Nil, List/Cons)
```

Importing only `List` from `List.bend` will import the type `List` and bind its constructors name `List/Nil` and `List/Cons`.

## Importing files with a top level name equal to its name

When a file and a top-level name in it share a name, for example, `List.bend`, `concat.bend` and `append.bend`, the bind of that import is simplified to the file name.

### Example:
```py
from types/List import append
```

This will bind `append` and `append/helper` from `append.bend`.

## Files and directories with the same name

When files and directories share a name, both share the import namespace:

```py
from types/List import (List, concat)
LunaAmora marked this conversation as resolved.
Show resolved Hide resolved
```

This will attempt to import from both the `List.bend` file and the `List` folder, resulting in the binds `List/Nil`, `List/Cons` and `concat`.

```py
from types/List import *
```

This will import all the names from `List.bend`, then all the files inside the `List` folder, resulting in the binds `List/Nil`, `List/Cons`, `concat`, `append` and `append/helper`.

In both cases, if a name is present as a top-level name on the file, and as a `.bend` file name inside the folder, it will result in an error.

If you only want to import `List.bend` and not search the files in its folder, you can use the `import path` syntax:

```py
import types/List
```
30 changes: 30 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -1234,3 +1234,33 @@ impossible to write in normal Bend syntax.
It will also ignore all term-level compiler passes and so can be
useful for writing programs with exact behaviour that won't ever be
changed or optimized by the compiler.

# Import Syntax

### Import Relative to the File
Paths starting with `./` or `../` are imported relative to the file.

### Import Relative to the Main Folder
Paths that do not start with `./` or `../` are relative to the folder of the main file.

## Syntax

### Import Specific Names from a File, or Files from a Folder
```py
from path import name
from path import (name1, name2)
import (path/name1, path/name2)
```

### Import All Names from a File, or All Files from a Folder
```py
from path import *
```

### Aliasing Imports
```py
from path import name as alias
from path import (name1 as Alias1, name2 as Alias2)
import path as alias
import (path/name1 as Alias1, path/name2 as Alias2)
```
9 changes: 9 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub struct DiagnosticsConfig {
pub unused_definition: Severity,
pub repeated_bind: Severity,
pub recursion_cycle: Severity,
pub missing_main: Severity,
pub import_shadow: Severity,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -57,6 +59,8 @@ pub enum WarningType {
UnusedDefinition,
RepeatedBind,
RecursionCycle,
MissingMain,
ImportShadow,
}

impl Diagnostics {
Expand Down Expand Up @@ -235,6 +239,9 @@ impl DiagnosticsConfig {
unused_definition: severity,
repeated_bind: severity,
recursion_cycle: severity,
import_shadow: severity,
// Should only be changed manually, as a missing main is always a error to hvm
missing_main: Severity::Error,
verbose,
}
}
Expand All @@ -247,6 +254,8 @@ impl DiagnosticsConfig {
WarningType::IrrefutableMatch => self.irrefutable_match,
WarningType::RedundantMatch => self.redundant_match,
WarningType::UnreachableMatch => self.unreachable_match,
WarningType::MissingMain => self.missing_main,
WarningType::ImportShadow => self.import_shadow,
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions src/fun/builtins.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::{parser::TermParser, Book, Name, Num, Pattern, Term};
use super::{
parser::{ParseBook, TermParser},
Book, Name, Num, Pattern, Term,
};
use crate::maybe_grow;

const BUILTINS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/fun/builtins.bend"));
Expand Down Expand Up @@ -42,13 +45,15 @@ pub const BUILTIN_CTRS: &[&str] =

pub const BUILTIN_TYPES: &[&str] = &[LIST, STRING, NAT, TREE, MAP, IO];

impl Book {
pub fn builtins() -> Book {
impl ParseBook {
pub fn builtins() -> Self {
TermParser::new(BUILTINS)
.parse_book(Book::default(), true)
.parse_book(Self::default(), true)
.expect("Error parsing builtin file, this should not happen")
}
}

impl Book {
pub fn encode_builtins(&mut self) {
for def in self.defs.values_mut() {
for rule in def.rules.iter_mut() {
Expand Down
3 changes: 2 additions & 1 deletion src/fun/check/set_entrypoint.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
diagnostics::WarningType,
fun::{Book, Ctx, Definition, Name},
ENTRY_POINT, HVM1_ENTRY_POINT,
};
Expand Down Expand Up @@ -43,7 +44,7 @@ impl Ctx<'_> {

(None, None, None) => {
let entrypoint = self.book.entrypoint.clone().unwrap_or(Name::new(ENTRY_POINT));
self.info.add_book_error(EntryErr::NotFound(entrypoint))
self.info.add_book_warning(EntryErr::NotFound(entrypoint), WarningType::MissingMain)
}
}

Expand Down
38 changes: 32 additions & 6 deletions src/fun/load_book.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
use crate::fun::{self, parser::TermParser};
use super::{
parser::{ParseBook, TermParser},
Book, Name,
};
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
imports::PackageLoader,
};
use std::path::Path;

// TODO: Refactor so that we don't mix the two syntaxes here.

/// Reads a file and parses to a definition book.
pub fn load_file_to_book(path: &Path) -> Result<fun::Book, String> {
let builtins = fun::Book::builtins();
pub fn load_file_to_book(
path: &Path,
package_loader: impl PackageLoader,
diag: DiagnosticsConfig,
) -> Result<Book, Diagnostics> {
let code = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
do_parse_book(&code, path, builtins)
load_to_book(path, &code, package_loader, diag)
}

pub fn do_parse_book(code: &str, path: &Path, builtins: fun::Book) -> Result<fun::Book, String> {
TermParser::new(code).parse_book(builtins, false).map_err(|e| format!("In {} :\n{}", path.display(), e))
pub fn load_to_book(
origin: &Path,
code: &str,
package_loader: impl PackageLoader,
diag: DiagnosticsConfig,
) -> Result<Book, Diagnostics> {
let builtins = ParseBook::builtins();
let book = do_parse_book(code, origin, builtins)?;
book.load_imports(package_loader, diag)
}

pub fn do_parse_book(code: &str, origin: &Path, mut book: ParseBook) -> Result<ParseBook, String> {
book.source = Name::new(origin.to_string_lossy());
TermParser::new(code).parse_book(book, false).map_err(|e| format!("In {} :\n{}", origin.display(), e))
}

pub fn do_parse_book_default(code: &str, origin: &Path) -> Result<Book, String> {
do_parse_book(code, origin, ParseBook::builtins())?.to_fun()
}
Loading
Loading