Skip to content

Commit

Permalink
Merge pull request #544 from HigherOrderCO/import-system
Browse files Browse the repository at this point in the history
Add import system
  • Loading branch information
developedby authored Jul 5, 2024
2 parents a2b75ff + cd2a58b commit 57d6798
Show file tree
Hide file tree
Showing 70 changed files with 1,978 additions and 184 deletions.
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)
```

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

0 comments on commit 57d6798

Please sign in to comment.