mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-11 10:55:31 +03:00
removed editor, ast, markup crate
This commit is contained in:
parent
a616b61503
commit
99cfd1fe92
5
.github/workflows/macos_x86_64.yml
vendored
5
.github/workflows/macos_x86_64.yml
vendored
@ -24,7 +24,4 @@ jobs:
|
||||
- name: regular rust tests
|
||||
run: cargo test --locked --release -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_str::str_append_scalar --skip gen_tags::phantom_polymorphic_record && sccache --show-stats
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- name: test launching the editor
|
||||
run: cargo test --release --locked editor_launch_test::launch -- --ignored # `--ignored` to run this test that is ignored for "normal" runs
|
||||
# this issue may be caused by using older versions of XCode
|
2
.github/workflows/nightly_linux_x86_64.yml
vendored
2
.github/workflows/nightly_linux_x86_64.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build release with lto
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299
|
||||
|
||||
- name: get commit SHA
|
||||
|
@ -36,7 +36,7 @@ jobs:
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build nightly release
|
||||
run: cargo build --locked --profile=release-with-lto --features "editor" --bin roc
|
||||
run: cargo build --locked --profile=release-with-lto --bin roc
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
|
4
.github/workflows/nightly_macos_x86_64.yml
vendored
4
.github/workflows/nightly_macos_x86_64.yml
vendored
@ -22,8 +22,8 @@ jobs:
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- name: build release
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
@ -29,6 +29,9 @@ jobs:
|
||||
- name: check code style with clippy --release
|
||||
run: cargo clippy --workspace --tests --release -- --deny warnings
|
||||
|
||||
- name: test building default.nix
|
||||
run: nix-build
|
||||
|
||||
- name: execute tests with --release
|
||||
run: nix develop -c cargo test --locked --release
|
||||
|
||||
@ -37,10 +40,7 @@ jobs:
|
||||
|
||||
- name: check that the platform`s produced dylib is loadable
|
||||
run: cd examples/platform-switching/rust-platform && nix develop -c cargo test --release --locked
|
||||
|
||||
- name: test launching the editor
|
||||
run: nix develop -c cargo test --release --locked editor_launch_test::launch -- --ignored # `--ignored` to run this test that is ignored for "normal" runs
|
||||
|
||||
|
||||
# we run the llvm wasm tests only on this machine because it is fast and wasm should be cross-target
|
||||
- name: execute llvm wasm tests with --release
|
||||
run: nix develop -c cargo test-gen-llvm-wasm --locked --release
|
||||
|
3
.github/workflows/nix_macos_x86_64.yml
vendored
3
.github/workflows/nix_macos_x86_64.yml
vendored
@ -22,6 +22,9 @@ jobs:
|
||||
|
||||
- uses: cachix/install-nix-action@v22
|
||||
|
||||
- name: test building default.nix
|
||||
run: nix-build
|
||||
|
||||
- name: execute cli_run tests only, the full tests take too long but are run nightly
|
||||
run: nix develop -c cargo test --locked --release -p roc_cli
|
||||
|
||||
|
5
.github/workflows/ubuntu_x86_64.yml
vendored
5
.github/workflows/ubuntu_x86_64.yml
vendored
@ -39,10 +39,7 @@ jobs:
|
||||
|
||||
- name: check that the platform`s produced dylib is loadable
|
||||
run: cd examples/platform-switching/rust-platform && LD_LIBRARY_PATH=. cargo test --release --locked
|
||||
|
||||
- name: test launching the editor
|
||||
run: cargo test --release --locked editor_launch_test::launch -- --ignored # `--ignored` to run this test that is ignored for "normal" runs
|
||||
|
||||
|
||||
- name: test the dev backend # these tests require an explicit feature flag
|
||||
run: cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
|
||||
|
4
.github/workflows/windows_tests.yml
vendored
4
.github/workflows/windows_tests.yml
vendored
@ -50,11 +50,11 @@ jobs:
|
||||
|
||||
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
|
||||
- name: Build specific tests without running. Twice for zig lld-link error.
|
||||
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen
|
||||
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen
|
||||
|
||||
- name: Test setjmp/longjmp logic
|
||||
run: cargo test-gen-dev --locked --release nat_alias && cargo test-gen-dev --locked --release a_crash
|
||||
|
||||
- name: Actually run the tests.
|
||||
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
|
||||
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli
|
||||
|
||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -38,18 +38,6 @@ metadata
|
||||
.vimrc
|
||||
.nvimrc
|
||||
|
||||
#files too big to track in git
|
||||
editor/benches/resources/100000_lines.roc
|
||||
editor/benches/resources/10000_lines.roc
|
||||
editor/benches/resources/1000_lines.roc
|
||||
editor/benches/resources/100_lines.roc
|
||||
editor/benches/resources/25000000_lines.roc
|
||||
editor/benches/resources/50000_lines.roc
|
||||
editor/benches/resources/500_lines.roc
|
||||
|
||||
# file editor creates when no arg is passed
|
||||
roc-projects
|
||||
|
||||
# rust cache (sccache folder)
|
||||
sccache_dir
|
||||
|
||||
|
@ -57,17 +57,6 @@ Read the instructions [here](devtools/README.md) to make nix work well with your
|
||||
If you want to load all dependencies automatically whenever you `cd` into `roc`, check out [direnv](https://direnv.net/).
|
||||
Then you will no longer need to execute `nix develop` first.
|
||||
|
||||
### Editor
|
||||
|
||||
The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
|
||||
`cargo run edit` should work on NixOS and MacOS. If you use Linux x86_64, follow the instructions below.
|
||||
|
||||
If you're not already in a nix shell, execute `nix develop` at the the root of the repo folder and then execute:
|
||||
|
||||
```sh
|
||||
nixVulkanIntel cargo run edit
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Create an issue if you run into problems not listed here.
|
||||
@ -190,7 +179,7 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
|
||||
|
||||
### LLVM installation on Windows
|
||||
|
||||
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows.
|
||||
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, and many tests will not work on windows.
|
||||
The official LLVM pre-built binaries for Windows lack features that roc needs. Instead:
|
||||
|
||||
1. Download the custom LLVM 7z archive [here](https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z).
|
||||
|
1549
Cargo.lock
generated
1549
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,8 @@ members = [
|
||||
"crates/compiler/*",
|
||||
"crates/vendor/*",
|
||||
"crates/glue",
|
||||
"crates/editor",
|
||||
"crates/ast",
|
||||
"crates/cli",
|
||||
"crates/cli_utils",
|
||||
"crates/code_markup",
|
||||
"crates/highlight",
|
||||
"crates/error_macros",
|
||||
"crates/reporting",
|
||||
|
@ -52,7 +52,7 @@ limitations under the License.
|
||||
|
||||
* Elm - https://github.com/elm/compiler
|
||||
|
||||
This source code can be found in editor/src/lang/solve.rs and compiler/src/solve.rs, and is licensed under the following terms:
|
||||
This source code can be found in compiler/src/solve.rs, and is licensed under the following terms:
|
||||
|
||||
|
||||
Copyright 2012-present Evan Czaplicki
|
||||
@ -119,23 +119,6 @@ limitations under the License.
|
||||
|
||||
===========================================================
|
||||
|
||||
* learn-wgpu - https://github.com/sotrh/learn-wgpu
|
||||
|
||||
This source code can be found in editor/src/graphics/lowlevel/buffer.rs, editor/src/graphics/primitives/text.rs, and editor/src/graphics/primitives/lowlevel/vertex.rs, and is licensed under the following terms:
|
||||
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Benjamin Hansen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
===========================================================
|
||||
|
||||
* pretty - https://github.com/Marwes/pretty.rs
|
||||
|
||||
This source code can be found in vendor/pretty/ and is licensed under the following terms:
|
||||
@ -186,21 +169,6 @@ limitations under the License.
|
||||
|
||||
===========================================================
|
||||
|
||||
* Ropey - https://github.com/cessen/ropey
|
||||
|
||||
This source code can be found in editor/src/ui/text/lines.rs and is licensed under the following terms:
|
||||
|
||||
|
||||
Copyright (c) 2017 Nathan Vegdahl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
===========================================================
|
||||
|
||||
* Zig - https://ziglang.org
|
||||
|
||||
This source code can be found in compiler/builtins/bitcode/src/hash.zig, highlight/tests/peg_grammar.rs and highlight/src/highlight_parser.rs and is licensed under the following terms:
|
||||
|
@ -8,10 +8,6 @@ You can use `cargo doc` to generate docs for a specific package; e.g.
|
||||
cargo doc --package roc_ast --open
|
||||
```
|
||||
|
||||
## `ast/` - `roc_ast`
|
||||
|
||||
Code to represent the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) as used by the editor. In contrast to the compiler, the types in this AST do not keep track of the location of the matching code in the source file.
|
||||
|
||||
## `cli/` - `roc_cli`
|
||||
|
||||
The `roc` binary that brings together all functionality in the Roc toolset.
|
||||
@ -20,10 +16,6 @@ The `roc` binary that brings together all functionality in the Roc toolset.
|
||||
|
||||
Provides shared code for cli tests and benchmarks.
|
||||
|
||||
## `code_markup/` - `roc_code_markup`
|
||||
|
||||
A [markup language](https://en.wikipedia.org/wiki/Markup_language) to display Roc code in the editor.
|
||||
|
||||
## `compiler/`
|
||||
|
||||
Compiles `.roc` files and combines them with their platform into an executable binary. See [compiler/README.md](./compiler/README.md) for more information.
|
||||
@ -76,10 +68,6 @@ Used for [roc-lang.org/builtins/Num](https://www.roc-lang.org/builtins/Num).
|
||||
|
||||
Provides a binary that is only used for static build servers.
|
||||
|
||||
## `editor/` - `roc_editor`
|
||||
|
||||
Roc's editor. See [README.md](./editor/README.md) for more information.
|
||||
|
||||
## `error_macros/` - `roc_error_macros`
|
||||
|
||||
Provides macros for consistent reporting of errors in Roc's rust code.
|
||||
@ -90,7 +78,7 @@ The `roc_glue` crate generates code needed for platform hosts to communicate wit
|
||||
|
||||
## `highlight/` - `roc_highlight`
|
||||
|
||||
Provides syntax highlighting for the editor by transforming a string to markup nodes.
|
||||
Provides syntax highlighting for the static site gen platform which is used by the tutorial.
|
||||
|
||||
## `linker/` - `roc_linker`
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
[package]
|
||||
name = "roc_ast"
|
||||
description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_checkmate = { path = "../compiler/checkmate" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_packaging = { path = "../packaging" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_problem = { path = "../compiler/problem" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
roc_solve_schema = { path = "../compiler/solve_schema" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_unify = { path = "../compiler/unify" }
|
||||
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
|
||||
arrayvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
libc.workspace = true
|
||||
page_size.workspace = true
|
||||
snafu.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi.workspace = true
|
@ -1,73 +0,0 @@
|
||||
use roc_module::{ident::Ident, module_err::ModuleError};
|
||||
use roc_parse::parser::SyntaxError;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use snafu::{Backtrace, Snafu};
|
||||
|
||||
use crate::lang::core::ast::ASTNodeId;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum ASTError {
|
||||
#[snafu(display(
|
||||
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expecting `Some(ExprId)` .",
|
||||
ast_node_id
|
||||
))]
|
||||
ASTNodeIdWithoutExprId {
|
||||
ast_node_id: ASTNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"UnexpectedASTNode: required a {} at this position, node was a {}.",
|
||||
required_node_type,
|
||||
encountered_node_type
|
||||
))]
|
||||
UnexpectedASTNode {
|
||||
required_node_type: String,
|
||||
encountered_node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.",
|
||||
required_pattern2,
|
||||
encountered_pattern2,
|
||||
))]
|
||||
UnexpectedPattern2Variant {
|
||||
required_pattern2: String,
|
||||
encountered_pattern2: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("IdentExistsError: {}", msg))]
|
||||
IdentExistsError { msg: String },
|
||||
|
||||
WrapModuleError {
|
||||
#[snafu(backtrace)]
|
||||
source: ModuleError,
|
||||
},
|
||||
|
||||
#[snafu(display("SyntaxError: {}", msg))]
|
||||
SyntaxErrorNoBacktrace { msg: String },
|
||||
}
|
||||
|
||||
pub type ASTResult<T, E = ASTError> = std::result::Result<T, E>;
|
||||
|
||||
impl From<ModuleError> for ASTError {
|
||||
fn from(module_err: ModuleError) -> Self {
|
||||
Self::WrapModuleError { source: module_err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Region, Loc<Ident>)> for ASTError {
|
||||
fn from(ident_exists_err: (Region, Loc<Ident>)) -> Self {
|
||||
Self::IdentExistsError {
|
||||
msg: format!("{ident_exists_err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<SyntaxError<'a>> for ASTError {
|
||||
fn from(syntax_err: SyntaxError) -> Self {
|
||||
Self::SyntaxErrorNoBacktrace {
|
||||
msg: format!("{syntax_err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,840 +0,0 @@
|
||||
use roc_collections::all::{default_hasher, ImMap, MutMap};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarId, Variable};
|
||||
use roc_types::types::{AliasKind, RecordField};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SolvedLambdaSet(pub SolvedType);
|
||||
|
||||
/// This is a fully solved type, with no Variables remaining in it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SolvedType {
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
#[allow(unused)]
|
||||
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),
|
||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||
#[allow(unused)]
|
||||
Apply(Symbol, Vec<SolvedType>),
|
||||
/// A bound type variable, e.g. `a` in `(a -> a)`
|
||||
#[allow(unused)]
|
||||
Rigid(Lowercase),
|
||||
Flex(VarId),
|
||||
#[allow(unused)]
|
||||
Wildcard,
|
||||
/// Inline type alias, e.g. `as List a` in `[Cons a (List a), Nil] as List a`
|
||||
#[allow(unused)]
|
||||
Record {
|
||||
fields: Vec<(Lowercase, RecordField<SolvedType>)>,
|
||||
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`.
|
||||
/// This is None if it's a closed record annotation like `{ name: Str }`.
|
||||
ext: Box<SolvedType>,
|
||||
},
|
||||
#[allow(unused)]
|
||||
EmptyRecord,
|
||||
TagUnion(Vec<(TagName, Vec<SolvedType>)>, Box<SolvedType>),
|
||||
#[allow(unused)]
|
||||
LambdaTag(Symbol, Vec<SolvedType>),
|
||||
#[allow(unused)]
|
||||
FunctionOrTagUnion(TagName, Symbol, Box<SolvedType>),
|
||||
#[allow(unused)]
|
||||
RecursiveTagUnion(VarId, Vec<(TagName, Vec<SolvedType>)>, Box<SolvedType>),
|
||||
EmptyTagUnion,
|
||||
/// A type from an Invalid module
|
||||
#[allow(unused)]
|
||||
Erroneous,
|
||||
|
||||
Alias(
|
||||
Symbol,
|
||||
Vec<SolvedType>,
|
||||
Vec<SolvedLambdaSet>,
|
||||
Box<SolvedType>,
|
||||
AliasKind,
|
||||
),
|
||||
|
||||
#[allow(unused)]
|
||||
HostExposedAlias {
|
||||
name: Symbol,
|
||||
arguments: Vec<SolvedType>,
|
||||
lambda_set_variables: Vec<SolvedLambdaSet>,
|
||||
actual_var: VarId,
|
||||
actual: Box<SolvedType>,
|
||||
},
|
||||
|
||||
/// A type error
|
||||
#[allow(unused)]
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuiltinAlias {
|
||||
pub region: Region,
|
||||
pub vars: Vec<Loc<Lowercase>>,
|
||||
pub typ: SolvedType,
|
||||
pub kind: AliasKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FreeVars {
|
||||
pub named_vars: ImMap<Lowercase, Variable>,
|
||||
pub unnamed_vars: ImMap<VarId, Variable>,
|
||||
pub wildcards: Vec<Variable>,
|
||||
}
|
||||
|
||||
const NUM_BUILTIN_IMPORTS: usize = 8;
|
||||
|
||||
/// These can be shared between definitions, they will get instantiated when converted to Type
|
||||
const TVAR1: VarId = VarId::from_u32(1);
|
||||
|
||||
pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
|
||||
let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
|
||||
|
||||
let mut add_alias = |symbol, alias| {
|
||||
debug_assert!(
|
||||
!aliases.contains_key(&symbol),
|
||||
"Duplicate alias definition for {symbol:?}"
|
||||
);
|
||||
|
||||
// TODO instead of using Region::zero for all of these,
|
||||
// instead use the Region where they were defined in their
|
||||
// source .roc files! This can give nicer error messages.
|
||||
aliases.insert(symbol, alias);
|
||||
};
|
||||
|
||||
// Int range : Num (Integer range)
|
||||
add_alias(
|
||||
Symbol::NUM_INT,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![Loc::at(Region::zero(), "range".into())],
|
||||
typ: int_alias_content(flex(TVAR1)),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Frac range : Num (FloatingPoint range)
|
||||
add_alias(
|
||||
Symbol::NUM_FRAC,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![Loc::at(Region::zero(), "range".into())],
|
||||
typ: frac_alias_content(flex(TVAR1)),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Num range := range
|
||||
add_alias(
|
||||
Symbol::NUM_NUM,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![Loc::at(Region::zero(), "range".into())],
|
||||
typ: num_alias_content(flex(TVAR1)),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Integer range := range
|
||||
add_alias(
|
||||
Symbol::NUM_INTEGER,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![Loc::at(Region::zero(), "range".into())],
|
||||
typ: integer_alias_content(flex(TVAR1)),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Natural := []
|
||||
add_alias(
|
||||
Symbol::NUM_NATURAL,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: natural_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Nat : Int Natural
|
||||
add_alias(
|
||||
Symbol::NUM_NAT,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: nat_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Signed128 := []
|
||||
add_alias(
|
||||
Symbol::NUM_SIGNED128,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: signed128_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// I128 : Int Signed128
|
||||
add_alias(
|
||||
Symbol::NUM_I128,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: i128_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// U128 : Int Unsigned128
|
||||
add_alias(
|
||||
Symbol::NUM_U128,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: u128_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Signed64 := []
|
||||
add_alias(
|
||||
Symbol::NUM_SIGNED64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: signed64_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// I64 : Int Signed64
|
||||
add_alias(
|
||||
Symbol::NUM_I64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: i64_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// U64 : Int Unsigned64
|
||||
add_alias(
|
||||
Symbol::NUM_U64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: u64_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Signed32 := []
|
||||
add_alias(
|
||||
Symbol::NUM_SIGNED32,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: signed32_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// I32 : Int Signed32
|
||||
add_alias(
|
||||
Symbol::NUM_I32,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: i32_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// U32 : Int Unsigned32
|
||||
add_alias(
|
||||
Symbol::NUM_U32,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: u32_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Signed16 := []
|
||||
add_alias(
|
||||
Symbol::NUM_SIGNED16,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: signed16_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// I16 : Int Signed16
|
||||
add_alias(
|
||||
Symbol::NUM_I16,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: i16_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// U16 : Int Unsigned16
|
||||
add_alias(
|
||||
Symbol::NUM_U16,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: u16_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Signed8 := []
|
||||
add_alias(
|
||||
Symbol::NUM_SIGNED8,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: signed8_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// I8 : Int Signed8
|
||||
add_alias(
|
||||
Symbol::NUM_I8,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: i8_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// U8 : Int Unsigned8
|
||||
add_alias(
|
||||
Symbol::NUM_U8,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: u8_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Decimal := []
|
||||
add_alias(
|
||||
Symbol::NUM_DECIMAL,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: decimal_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Binary64 := []
|
||||
add_alias(
|
||||
Symbol::NUM_BINARY64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: binary64_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Binary32 := []
|
||||
add_alias(
|
||||
Symbol::NUM_BINARY32,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: binary32_alias_content(),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// FloatingPoint range := range
|
||||
add_alias(
|
||||
Symbol::NUM_FLOATINGPOINT,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![Loc::at(Region::zero(), "range".into())],
|
||||
typ: floatingpoint_alias_content(flex(TVAR1)),
|
||||
kind: AliasKind::Opaque,
|
||||
},
|
||||
);
|
||||
|
||||
// Dec : Frac Decimal
|
||||
add_alias(
|
||||
Symbol::NUM_DEC,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: dec_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// F64 : Frac Binary64
|
||||
add_alias(
|
||||
Symbol::NUM_F64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: f64_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// F32 : Frac Binary32
|
||||
add_alias(
|
||||
Symbol::NUM_F32,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: f32_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Bool : [True, False]
|
||||
add_alias(
|
||||
Symbol::BOOL_BOOL,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: bool_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Utf8ByteProblem : [InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation, OverlongEncoding, CodepointTooLarge, EncodesSurrogateHalf]
|
||||
add_alias(
|
||||
Symbol::STR_UT8_BYTE_PROBLEM,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: str_utf8_byte_problem_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
// Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
|
||||
add_alias(
|
||||
Symbol::STR_UT8_PROBLEM,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: str_utf8_byte_problem_alias_content(),
|
||||
kind: AliasKind::Structural,
|
||||
},
|
||||
);
|
||||
|
||||
aliases
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn flex(tvar: VarId) -> SolvedType {
|
||||
SolvedType::Flex(tvar)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_type(range: SolvedType) -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_NUM,
|
||||
vec![(range.clone())],
|
||||
vec![],
|
||||
Box::new(num_alias_content(range)),
|
||||
AliasKind::Opaque,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_alias_content(range: SolvedType) -> SolvedType {
|
||||
range
|
||||
}
|
||||
|
||||
// FLOATING POINT
|
||||
|
||||
#[inline(always)]
|
||||
pub fn floatingpoint_type(range: SolvedType) -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_FLOATINGPOINT,
|
||||
vec![(range.clone())],
|
||||
vec![],
|
||||
Box::new(floatingpoint_alias_content(range)),
|
||||
AliasKind::Opaque,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn floatingpoint_alias_content(range: SolvedType) -> SolvedType {
|
||||
range
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn frac_alias_content(range: SolvedType) -> SolvedType {
|
||||
num_type(floatingpoint_type(range))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn f64_alias_content() -> SolvedType {
|
||||
frac_alias_content(binary64_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn f32_alias_content() -> SolvedType {
|
||||
frac_alias_content(binary32_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn nat_alias_content() -> SolvedType {
|
||||
int_alias_content(natural_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn i128_alias_content() -> SolvedType {
|
||||
int_alias_content(signed128_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u128_alias_content() -> SolvedType {
|
||||
int_alias_content(unsigned128_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u64_alias_content() -> SolvedType {
|
||||
int_alias_content(unsigned64_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn i64_alias_content() -> SolvedType {
|
||||
int_alias_content(signed64_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u32_alias_content() -> SolvedType {
|
||||
int_alias_content(unsigned32_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn i32_alias_content() -> SolvedType {
|
||||
int_alias_content(signed32_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u16_alias_content() -> SolvedType {
|
||||
int_alias_content(unsigned16_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn i16_alias_content() -> SolvedType {
|
||||
int_alias_content(signed16_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u8_alias_content() -> SolvedType {
|
||||
int_alias_content(unsigned8_type())
|
||||
}
|
||||
|
||||
// I8
|
||||
|
||||
#[inline(always)]
|
||||
fn i8_alias_content() -> SolvedType {
|
||||
int_alias_content(signed8_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn int_alias_content(range: SolvedType) -> SolvedType {
|
||||
num_type(integer_type(range))
|
||||
}
|
||||
|
||||
// INTEGER
|
||||
|
||||
#[inline(always)]
|
||||
pub fn integer_type(range: SolvedType) -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_INTEGER,
|
||||
vec![(range.clone())],
|
||||
vec![],
|
||||
Box::new(integer_alias_content(range)),
|
||||
AliasKind::Opaque,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn integer_alias_content(range: SolvedType) -> SolvedType {
|
||||
range
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn binary64_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_BINARY64,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(binary64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn binary64_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn binary32_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_BINARY32,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(binary32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn binary32_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn natural_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_NATURAL,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(natural_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn natural_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn signed128_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_SIGNED128,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(signed128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn signed128_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn signed64_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_SIGNED64,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(signed64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn signed64_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn signed32_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_SIGNED32,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(signed32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn signed32_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn signed16_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_SIGNED16,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(signed16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn signed16_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn signed8_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_SIGNED8,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(signed8_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn signed8_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unsigned128_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_UNSIGNED128,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unsigned128_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unsigned64_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_UNSIGNED64,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unsigned64_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unsigned32_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_UNSIGNED32,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unsigned32_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unsigned16_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_UNSIGNED16,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unsigned16_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unsigned8_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_UNSIGNED8,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned8_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unsigned8_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn decimal_alias_content() -> SolvedType {
|
||||
SolvedType::EmptyTagUnion
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn dec_alias_content() -> SolvedType {
|
||||
frac_alias_content(decimal_type())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn decimal_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_DECIMAL,
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(decimal_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
fn bool_alias_content() -> SolvedType {
|
||||
SolvedType::TagUnion(
|
||||
vec![
|
||||
(TagName("False".into()), vec![]),
|
||||
(TagName("True".into()), vec![]),
|
||||
],
|
||||
Box::new(SolvedType::EmptyTagUnion),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn str_utf8_byte_problem_alias_content() -> SolvedType {
|
||||
// 1. This must have the same values as the Zig struct Utf8ByteProblem in src/str.zig
|
||||
// 2. This must be in alphabetical order
|
||||
//
|
||||
// [CodepointTooLarge, EncodesSurrogateHalf, OverlongEncoding, InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation]
|
||||
SolvedType::TagUnion(
|
||||
vec![
|
||||
(TagName("CodepointTooLarge".into()), vec![]),
|
||||
(TagName("EncodesSurrogateHalf".into()), vec![]),
|
||||
(TagName("ExpectedContinuation".into()), vec![]),
|
||||
(TagName("InvalidStartByte".into()), vec![]),
|
||||
(TagName("OverlongEncoding".into()), vec![]),
|
||||
(TagName("UnexpectedEndOfSequence".into()), vec![]),
|
||||
],
|
||||
Box::new(SolvedType::EmptyTagUnion),
|
||||
)
|
||||
}
|
@ -1,316 +0,0 @@
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use crate::{
|
||||
lang::{
|
||||
core::{
|
||||
def::def::References,
|
||||
expr::{
|
||||
expr2::{Expr2, ExprId, WhenBranch},
|
||||
expr_to_expr2::expr_to_expr2,
|
||||
output::Output,
|
||||
record_field::RecordField,
|
||||
},
|
||||
pattern::to_pattern2,
|
||||
},
|
||||
env::Env,
|
||||
scope::Scope,
|
||||
},
|
||||
mem_pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
|
||||
};
|
||||
|
||||
pub(crate) enum CanonicalizeRecordProblem {
|
||||
#[allow(dead_code)]
|
||||
InvalidOptionalValue {
|
||||
field_name: PoolStr,
|
||||
field_region: Region,
|
||||
record_region: Region,
|
||||
},
|
||||
}
|
||||
|
||||
enum FieldVar {
|
||||
VarAndExprId(Variable, ExprId),
|
||||
OnlyVar(Variable),
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_fields<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
fields: &'a [Loc<roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>>],
|
||||
) -> Result<(PoolVec<RecordField>, Output), CanonicalizeRecordProblem> {
|
||||
let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default();
|
||||
let mut output = Output::default();
|
||||
|
||||
for loc_field in fields.iter() {
|
||||
match canonicalize_field(env, scope, &loc_field.value) {
|
||||
Ok(can_field) => {
|
||||
match can_field {
|
||||
CanonicalField::LabelAndValue {
|
||||
label,
|
||||
value_expr,
|
||||
value_output,
|
||||
var,
|
||||
} => {
|
||||
let expr_id = env.pool.add(value_expr);
|
||||
|
||||
let replaced =
|
||||
can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id));
|
||||
|
||||
if let Some(_old) = replaced {
|
||||
// env.problems.push(Problem::DuplicateRecordFieldValue {
|
||||
// field_name: label,
|
||||
// field_region: loc_field.region,
|
||||
// record_region: region,
|
||||
// replaced_region: old.region,
|
||||
// });
|
||||
todo!()
|
||||
}
|
||||
|
||||
output.references.union_mut(value_output.references);
|
||||
}
|
||||
CanonicalField::InvalidLabelOnly { label, var } => {
|
||||
let replaced = can_fields.insert(label, FieldVar::OnlyVar(var));
|
||||
|
||||
if let Some(_old) = replaced {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(CanonicalizeFieldProblem::InvalidOptionalValue {
|
||||
field_name: _,
|
||||
field_region: _,
|
||||
}) => {
|
||||
// env.problem(Problem::InvalidOptionalValue {
|
||||
// field_name: field_name.clone(),
|
||||
// field_region,
|
||||
// record_region: region,
|
||||
// });
|
||||
// return Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
// field_name,
|
||||
// field_region,
|
||||
// record_region: region,
|
||||
// });
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool);
|
||||
|
||||
for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) {
|
||||
let name = PoolStr::new(string, env.pool);
|
||||
|
||||
match field_var {
|
||||
FieldVar::VarAndExprId(var, expr_id) => {
|
||||
env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id);
|
||||
}
|
||||
FieldVar::OnlyVar(var) => {
|
||||
env.pool[node_id] = RecordField::InvalidLabelOnly(name, var);
|
||||
} // TODO RecordField::LabelOnly
|
||||
}
|
||||
}
|
||||
|
||||
Ok((pool_vec, output))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum CanonicalizeFieldProblem {
|
||||
InvalidOptionalValue {
|
||||
field_name: PoolStr,
|
||||
field_region: Region,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: the `value_output: Output` field takes _a lot_ of space!
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum CanonicalField<'a> {
|
||||
LabelAndValue {
|
||||
label: &'a str,
|
||||
value_expr: Expr2,
|
||||
value_output: Output,
|
||||
var: Variable,
|
||||
},
|
||||
InvalidLabelOnly {
|
||||
label: &'a str,
|
||||
var: Variable,
|
||||
}, // TODO make ValidLabelOnly
|
||||
}
|
||||
|
||||
fn canonicalize_field<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>,
|
||||
) -> Result<CanonicalField<'a>, CanonicalizeFieldProblem> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
||||
match field {
|
||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||
RequiredValue(label, _, loc_expr) => {
|
||||
let field_var = env.var_store.fresh();
|
||||
let (loc_can_expr, output) =
|
||||
expr_to_expr2(env, scope, &loc_expr.value, loc_expr.region);
|
||||
|
||||
match loc_can_expr {
|
||||
Expr2::RuntimeError() => Ok(CanonicalField::InvalidLabelOnly {
|
||||
label: label.value,
|
||||
var: field_var,
|
||||
}),
|
||||
_ => Ok(CanonicalField::LabelAndValue {
|
||||
label: label.value,
|
||||
value_expr: loc_can_expr,
|
||||
value_output: output,
|
||||
var: field_var,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {
|
||||
field_name: PoolStr::new(label.value, env.pool),
|
||||
field_region: Region::span_across(&label.region, &loc_expr.region),
|
||||
}),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(label) => {
|
||||
let field_var = env.var_store.fresh();
|
||||
// TODO return ValidLabel if label points to in scope variable
|
||||
Ok(CanonicalField::InvalidLabelOnly {
|
||||
label: label.value,
|
||||
var: field_var,
|
||||
})
|
||||
}
|
||||
|
||||
SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => {
|
||||
canonicalize_field(env, scope, sub_field)
|
||||
}
|
||||
|
||||
Malformed(_string) => {
|
||||
panic!("TODO canonicalize malformed record field");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn canonicalize_when_branch<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
branch: &'a roc_parse::ast::WhenBranch<'a>,
|
||||
output: &mut Output,
|
||||
) -> (WhenBranch, References) {
|
||||
let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool);
|
||||
|
||||
let original_scope = scope;
|
||||
let mut scope = original_scope.shallow_clone();
|
||||
|
||||
// TODO report symbols not bound in all patterns
|
||||
for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) {
|
||||
let (new_output, can_pattern) = to_pattern2(
|
||||
env,
|
||||
&mut scope,
|
||||
roc_parse::pattern::PatternType::WhenBranch,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
);
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
env.set_region(node_id, loc_pattern.region);
|
||||
env.pool[node_id] = can_pattern;
|
||||
}
|
||||
|
||||
let (value, mut branch_output) =
|
||||
expr_to_expr2(env, &mut scope, &branch.value.value, branch.value.region);
|
||||
let value_id = env.pool.add(value);
|
||||
env.set_region(value_id, branch.value.region);
|
||||
|
||||
let guard = match &branch.guard {
|
||||
None => None,
|
||||
Some(loc_expr) => {
|
||||
let (can_guard, guard_branch_output) =
|
||||
expr_to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region);
|
||||
|
||||
let expr_id = env.pool.add(can_guard);
|
||||
env.set_region(expr_id, loc_expr.region);
|
||||
|
||||
branch_output.union(guard_branch_output);
|
||||
Some(expr_id)
|
||||
}
|
||||
};
|
||||
|
||||
// Now that we've collected all the references for this branch, check to see if
|
||||
// any of the new idents it defined were unused. If any were, report it.
|
||||
for (symbol, region) in scope.symbols() {
|
||||
let symbol = symbol;
|
||||
|
||||
if !output.references.has_lookup(symbol)
|
||||
&& !branch_output.references.has_lookup(symbol)
|
||||
&& !original_scope.contains_symbol(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
}
|
||||
|
||||
let references = branch_output.references.clone();
|
||||
output.union(branch_output);
|
||||
|
||||
(
|
||||
WhenBranch {
|
||||
patterns,
|
||||
body: value_id,
|
||||
guard,
|
||||
},
|
||||
references,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_lookup(
|
||||
env: &mut Env<'_>,
|
||||
scope: &mut Scope,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
region: Region,
|
||||
) -> (Expr2, Output) {
|
||||
use Expr2::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
let can_expr = if module_name.is_empty() {
|
||||
// Since module_name was empty, this is an unqualified var.
|
||||
// Look it up in scope!
|
||||
match scope.lookup(&(*ident).into(), region) {
|
||||
Ok(symbol) => {
|
||||
output.references.lookups.insert(symbol);
|
||||
|
||||
Var(symbol)
|
||||
}
|
||||
Err(problem) => {
|
||||
env.problem(Problem::RuntimeError(problem));
|
||||
|
||||
RuntimeError()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Since module_name was nonempty, this is a qualified var.
|
||||
// Look it up in the env!
|
||||
match env.qualified_lookup(module_name, ident, region) {
|
||||
Ok(symbol) => {
|
||||
output.references.lookups.insert(symbol);
|
||||
|
||||
Var(symbol)
|
||||
}
|
||||
Err(problem) => {
|
||||
// Either the module wasn't imported, or
|
||||
// it was imported but it doesn't expose this ident.
|
||||
env.problem(Problem::RuntimeError(problem));
|
||||
|
||||
RuntimeError()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If it's valid, this ident should be in scope already.
|
||||
|
||||
(can_expr, output)
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod canonicalize;
|
||||
pub mod module;
|
@ -1,41 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::IdentIdsByModule;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use roc_parse::ast;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
|
||||
use crate::lang::core::def::def::canonicalize_defs;
|
||||
use crate::lang::core::def::def::Def;
|
||||
use crate::lang::core::def::def::{sort_can_defs, Declaration};
|
||||
use crate::lang::core::expr::expr2::Expr2;
|
||||
use crate::lang::core::expr::output::Output;
|
||||
use crate::lang::core::pattern::Pattern2;
|
||||
use crate::lang::core::types::Alias;
|
||||
use crate::lang::core::val_def::ValueDef;
|
||||
use crate::lang::env::Env;
|
||||
use crate::lang::scope::Scope;
|
||||
use crate::mem_pool::pool::NodeId;
|
||||
use crate::mem_pool::pool::Pool;
|
||||
use crate::mem_pool::pool_vec::PoolVec;
|
||||
use crate::mem_pool::shallow_clone::ShallowClone;
|
||||
|
||||
pub struct ModuleOutput {
|
||||
pub aliases: MutMap<Symbol, NodeId<Alias>>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
||||
pub lookups: Vec<(Symbol, Variable, Region)>,
|
||||
pub problems: Vec<Problem>,
|
||||
pub ident_ids: IdentIds,
|
||||
pub references: MutSet<Symbol>,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,63 +0,0 @@
|
||||
use crate::{
|
||||
ast_error::{ASTNodeIdWithoutExprIdSnafu, ASTResult},
|
||||
mem_pool::pool::Pool,
|
||||
};
|
||||
|
||||
use super::{
|
||||
def::def2::{def2_to_string, DefId},
|
||||
expr::{expr2::ExprId, expr2_to_string::expr2_to_string},
|
||||
header::AppHeader,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AST {
|
||||
pub header: AppHeader,
|
||||
pub def_ids: Vec<DefId>,
|
||||
}
|
||||
|
||||
impl AST {
|
||||
pub fn insert_def_at_index(&mut self, new_def_id: DefId, index: usize) {
|
||||
self.def_ids.insert(index, new_def_id);
|
||||
}
|
||||
|
||||
// TODO print in tree shape, similar to linux tree command
|
||||
pub fn ast_to_string(&self, pool: &Pool) -> String {
|
||||
let mut full_ast_string = String::new();
|
||||
|
||||
for def_id in self.def_ids.iter() {
|
||||
full_ast_string.push_str(&def2_to_string(*def_id, pool));
|
||||
full_ast_string.push_str("\n\n");
|
||||
}
|
||||
|
||||
full_ast_string
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum ASTNodeId {
|
||||
ADefId(DefId),
|
||||
AExprId(ExprId),
|
||||
}
|
||||
|
||||
impl ASTNodeId {
|
||||
pub fn to_expr_id(&self) -> ASTResult<ExprId> {
|
||||
match self {
|
||||
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
|
||||
_ => ASTNodeIdWithoutExprIdSnafu { ast_node_id: *self }.fail()?,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_def_id(&self) -> ASTResult<DefId> {
|
||||
match self {
|
||||
ASTNodeId::ADefId(def_id) => Ok(*def_id),
|
||||
_ => ASTNodeIdWithoutExprIdSnafu { ast_node_id: *self }.fail()?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String {
|
||||
match node_id {
|
||||
ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool),
|
||||
ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool),
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use roc_types::subs::VarStore;
|
||||
|
||||
use crate::{
|
||||
lang::core::{def::def::Def, expr::expr2::Expr2},
|
||||
mem_pool::{pool::Pool, pool_vec::PoolVec},
|
||||
};
|
||||
|
||||
use super::def::def::Declaration;
|
||||
|
||||
pub(crate) fn decl_to_let(
|
||||
pool: &mut Pool,
|
||||
var_store: &mut VarStore,
|
||||
decl: Declaration,
|
||||
ret: Expr2,
|
||||
) -> Expr2 {
|
||||
match decl {
|
||||
Declaration::Declare(def) => match def {
|
||||
Def::AnnotationOnly { .. } => todo!(),
|
||||
Def::Value(value_def) => {
|
||||
let def_id = pool.add(value_def);
|
||||
|
||||
let body_id = pool.add(ret);
|
||||
|
||||
Expr2::LetValue {
|
||||
def_id,
|
||||
body_id,
|
||||
body_var: var_store.fresh(),
|
||||
}
|
||||
}
|
||||
Def::Function(function_def) => {
|
||||
let def_id = pool.add(function_def);
|
||||
let body_id = pool.add(ret);
|
||||
|
||||
Expr2::LetFunction {
|
||||
def_id,
|
||||
body_id,
|
||||
body_var: var_store.fresh(),
|
||||
}
|
||||
}
|
||||
},
|
||||
Declaration::DeclareRec(defs) => {
|
||||
let mut function_defs = vec![];
|
||||
|
||||
for def in defs {
|
||||
match def {
|
||||
Def::AnnotationOnly { .. } => todo!(),
|
||||
Def::Function(function_def) => function_defs.push(function_def),
|
||||
Def::Value(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let body_id = pool.add(ret);
|
||||
|
||||
Expr2::LetRec {
|
||||
defs: PoolVec::new(function_defs.into_iter(), pool),
|
||||
body_var: var_store.fresh(),
|
||||
body_id,
|
||||
}
|
||||
}
|
||||
Declaration::InvalidCycle(_entries, _) => {
|
||||
// TODO: replace with something from Expr2
|
||||
// Expr::RuntimeError(RuntimeError::CircularDef(entries))
|
||||
todo!()
|
||||
}
|
||||
Declaration::Builtin(_) => {
|
||||
// Builtins should only be added to top-level decls, not to let-exprs!
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
use roc_module::symbol::IdentId;
|
||||
use std::fmt::Write as _; // import without risk of name clashing
|
||||
|
||||
use crate::{
|
||||
lang::core::expr::{expr2::Expr2, expr2_to_string::expr2_to_string},
|
||||
mem_pool::pool::{NodeId, Pool},
|
||||
};
|
||||
|
||||
// A top level definition, not inside a function. For example: `main = "Hello, world!"`
|
||||
#[derive(Debug)]
|
||||
pub enum Def2 {
|
||||
// ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!"
|
||||
ValueDef {
|
||||
identifier_id: IdentId,
|
||||
expr_id: NodeId<Expr2>,
|
||||
},
|
||||
Blank,
|
||||
CommentsBefore {
|
||||
comments: String,
|
||||
def_id: DefId,
|
||||
},
|
||||
CommentsAfter {
|
||||
comments: String,
|
||||
def_id: DefId,
|
||||
},
|
||||
}
|
||||
|
||||
pub type DefId = NodeId<Def2>;
|
||||
|
||||
pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String {
|
||||
let mut full_string = String::new();
|
||||
let def2 = pool.get(node_id);
|
||||
|
||||
match def2 {
|
||||
Def2::ValueDef {
|
||||
identifier_id,
|
||||
expr_id,
|
||||
} => {
|
||||
let _ = write!(
|
||||
full_string,
|
||||
"Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})",
|
||||
identifier_id,
|
||||
expr2_to_string(*expr_id, pool)
|
||||
);
|
||||
}
|
||||
Def2::Blank => {
|
||||
full_string.push_str("Def2::Blank");
|
||||
}
|
||||
Def2::CommentsBefore {
|
||||
comments,
|
||||
def_id: _,
|
||||
} => full_string.push_str(comments),
|
||||
Def2::CommentsAfter {
|
||||
comments,
|
||||
def_id: _,
|
||||
} => full_string.push_str(comments),
|
||||
}
|
||||
|
||||
full_string
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_parse::{ast::CommentOrNewline, parser::SyntaxError};
|
||||
use roc_region::all::Region;
|
||||
|
||||
use crate::lang::{core::expr::expr_to_expr2::loc_expr_to_expr2, env::Env, scope::Scope};
|
||||
|
||||
use super::def2::Def2;
|
||||
|
||||
fn spaces_to_comments(spaces: &[CommentOrNewline]) -> Option<String> {
|
||||
if !spaces.is_empty() && !all_newlines(spaces) {
|
||||
let mut all_comments_str = String::new();
|
||||
|
||||
for comment in spaces.iter().filter(|c_or_nl| !c_or_nl.is_newline()) {
|
||||
all_comments_str.push_str(&comment.to_string_repr());
|
||||
}
|
||||
|
||||
Some(all_comments_str)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel_defs_to_defs2<'a>(
|
||||
arena: &'a Bump,
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
parsed_defs: roc_parse::ast::Defs<'a>,
|
||||
region: Region,
|
||||
) -> Vec<Def2> {
|
||||
let mut result = Vec::with_capacity(parsed_defs.tags.len());
|
||||
|
||||
for (index, def) in parsed_defs.defs().enumerate() {
|
||||
let mut def = match def {
|
||||
Err(roc_parse::ast::ValueDef::Body(&loc_pattern, &loc_expr)) => {
|
||||
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
|
||||
let expr_id = env.pool.add(expr2);
|
||||
|
||||
use roc_parse::ast::Pattern::*;
|
||||
|
||||
match loc_pattern.value {
|
||||
Identifier(id_str) => {
|
||||
let identifier_id = env.ident_ids.get_or_insert(id_str);
|
||||
|
||||
// TODO support with annotation
|
||||
Def2::ValueDef {
|
||||
identifier_id,
|
||||
expr_id,
|
||||
}
|
||||
}
|
||||
other => {
|
||||
unimplemented!(
|
||||
"I don't yet know how to convert the pattern {:?} into an expr2",
|
||||
other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
other => {
|
||||
unimplemented!(
|
||||
"I don't know how to make an expr2 from this def yet: {:?}",
|
||||
other
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let spaces_before = &parsed_defs.spaces[parsed_defs.space_before[index].indices()];
|
||||
let spaces_after = &parsed_defs.spaces[parsed_defs.space_after[index].indices()];
|
||||
|
||||
if let Some(comments) = spaces_to_comments(spaces_before) {
|
||||
let inner_def_id = env.pool.add(def);
|
||||
def = Def2::CommentsBefore {
|
||||
comments,
|
||||
def_id: inner_def_id,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(comments) = spaces_to_comments(spaces_after) {
|
||||
let inner_def_id = env.pool.add(def);
|
||||
def = Def2::CommentsAfter {
|
||||
comments,
|
||||
def_id: inner_def_id,
|
||||
};
|
||||
}
|
||||
|
||||
result.push(def)
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn all_newlines(comments: &[CommentOrNewline]) -> bool {
|
||||
comments
|
||||
.iter()
|
||||
.all(|com_or_newline| com_or_newline.is_newline())
|
||||
}
|
||||
|
||||
pub fn str_to_def2<'a>(
|
||||
arena: &'a Bump,
|
||||
input: &'a str,
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
) -> Result<Vec<Def2>, SyntaxError<'a>> {
|
||||
match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) {
|
||||
Ok(defs) => Ok(toplevel_defs_to_defs2(arena, env, scope, defs, region)),
|
||||
Err(fail) => Err(fail),
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub mod def;
|
||||
pub mod def2;
|
||||
pub mod def_to_def2;
|
@ -1,228 +0,0 @@
|
||||
use arrayvec::ArrayString;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use crate::{
|
||||
lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef},
|
||||
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
|
||||
};
|
||||
use roc_can::expr::Recursive;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use super::record_field::RecordField;
|
||||
|
||||
pub const ARR_STRING_CAPACITY: usize = 24;
|
||||
pub type ArrString = ArrayString<ARR_STRING_CAPACITY>;
|
||||
|
||||
// TODO make the inner types private?
|
||||
pub type ExprId = NodeId<Expr2>;
|
||||
|
||||
/// An Expr that fits in 32B.
|
||||
/// It has a 1B discriminant and variants which hold payloads of at most 31B.
|
||||
#[derive(Debug)]
|
||||
pub enum Expr2 {
|
||||
/// A negative number literal without a dot
|
||||
SmallInt {
|
||||
number: IntVal, // 16B
|
||||
var: Variable, // 4B
|
||||
style: IntStyle, // 1B
|
||||
text: PoolStr, // 8B
|
||||
},
|
||||
// TODO(rvcas): rename this eventually
|
||||
/// A large (over 64-bit) negative number literal without a dot.
|
||||
/// This variant can't use IntVal because if IntVal stored 128-bit
|
||||
/// integers, it would be 32B on its own because of alignment.
|
||||
I128 {
|
||||
number: i128, // 16B
|
||||
var: Variable, // 4B
|
||||
style: IntStyle, // 1B
|
||||
text: PoolStr, // 8B
|
||||
},
|
||||
// TODO(rvcas): rename this eventually
|
||||
/// A large (over 64-bit) nonnegative number literal without a dot
|
||||
/// This variant can't use IntVal because if IntVal stored 128-bit
|
||||
/// integers, it would be 32B on its own because of alignment.
|
||||
U128 {
|
||||
number: u128, // 16B
|
||||
var: Variable, // 4B
|
||||
style: IntStyle, // 1B
|
||||
text: PoolStr, // 8B
|
||||
},
|
||||
/// A floating-point literal (with a dot)
|
||||
Float {
|
||||
number: FloatVal, // 16B
|
||||
var: Variable, // 4B
|
||||
text: PoolStr, // 8B
|
||||
},
|
||||
/// string literals of length up to 30B
|
||||
SmallStr(ArrString), // 31B
|
||||
/// string literals of length 31B or more
|
||||
Str(PoolStr), // 8B
|
||||
// Lookups
|
||||
Var(Symbol), // 8B
|
||||
InvalidLookup(PoolStr), // 8B
|
||||
|
||||
List {
|
||||
elem_var: Variable, // 4B
|
||||
elems: PoolVec<ExprId>, // 8B
|
||||
},
|
||||
If {
|
||||
cond_var: Variable, // 4B
|
||||
expr_var: Variable, // 4B
|
||||
branches: PoolVec<(ExprId, ExprId)>, // 8B
|
||||
final_else: ExprId, // 4B
|
||||
},
|
||||
When {
|
||||
cond_var: Variable, // 4B
|
||||
expr_var: Variable, // 4B
|
||||
branches: PoolVec<WhenBranch>, // 8B
|
||||
cond: ExprId, // 4B
|
||||
},
|
||||
LetRec {
|
||||
defs: PoolVec<FunctionDef>, // 8B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetFunction {
|
||||
def_id: NodeId<FunctionDef>, // 4B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetValue {
|
||||
def_id: NodeId<ValueDef>, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
body_var: Variable, // 4B
|
||||
},
|
||||
Call {
|
||||
args: PoolVec<(Variable, ExprId)>, // 8B
|
||||
expr_id: ExprId, // 4B
|
||||
expr_var: Variable, // 4B
|
||||
fn_var: Variable, // 4B
|
||||
closure_var: Variable, // 4B
|
||||
called_via: CalledVia, // 2B
|
||||
},
|
||||
RunLowLevel {
|
||||
op: LowLevel, // 1B
|
||||
args: PoolVec<(Variable, ExprId)>, // 8B
|
||||
ret_var: Variable, // 4B
|
||||
},
|
||||
Closure {
|
||||
args: PoolVec<(Variable, NodeId<Pattern2>)>, // 8B
|
||||
uniq_symbol: Symbol, // 8B This is a globally unique symbol for the closure
|
||||
body_id: ExprId, // 4B
|
||||
function_type: Variable, // 4B
|
||||
recursive: Recursive, // 1B
|
||||
extra: NodeId<ClosureExtra>, // 4B
|
||||
},
|
||||
// Product Types
|
||||
Record {
|
||||
record_var: Variable, // 4B
|
||||
fields: PoolVec<RecordField>, // 8B
|
||||
},
|
||||
/// Empty record constant
|
||||
EmptyRecord,
|
||||
/// Look up exactly one field on a record, e.g. (expr).foo.
|
||||
Access {
|
||||
field: PoolStr, // 4B
|
||||
expr: ExprId, // 4B
|
||||
record_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
field_var: Variable, // 4B
|
||||
},
|
||||
|
||||
/// field accessor as a function, e.g. (.foo) expr
|
||||
Accessor {
|
||||
function_var: Variable, // 4B
|
||||
closure_var: Variable, // 4B
|
||||
field: PoolStr, // 4B
|
||||
record_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
field_var: Variable, // 4B
|
||||
},
|
||||
Update {
|
||||
symbol: Symbol, // 8B
|
||||
updates: PoolVec<RecordField>, // 8B
|
||||
record_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
},
|
||||
|
||||
// Sum Types
|
||||
Tag {
|
||||
name: PoolStr, // 4B
|
||||
variant_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
arguments: PoolVec<(Variable, ExprId)>, // 8B
|
||||
},
|
||||
Blank, // Rendered as empty box in editor
|
||||
|
||||
// Compiles, but will crash if reached
|
||||
RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Problem {
|
||||
RanOutOfNodeIds,
|
||||
}
|
||||
|
||||
pub type Res<T> = Result<T, Problem>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum IntStyle {
|
||||
Decimal,
|
||||
Octal,
|
||||
Hex,
|
||||
Binary,
|
||||
}
|
||||
|
||||
impl IntStyle {
|
||||
pub fn from_base(base: roc_parse::ast::Base) -> Self {
|
||||
use roc_parse::ast::Base;
|
||||
match base {
|
||||
Base::Decimal => Self::Decimal,
|
||||
Base::Octal => Self::Octal,
|
||||
Base::Hex => Self::Hex,
|
||||
Base::Binary => Self::Binary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum IntVal {
|
||||
I64(i64),
|
||||
U64(u64),
|
||||
I32(i32),
|
||||
U32(u32),
|
||||
I16(i16),
|
||||
U16(u16),
|
||||
I8(i8),
|
||||
U8(u8),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_of_intval() {
|
||||
assert_eq!(std::mem::size_of::<IntVal>(), 16);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum FloatVal {
|
||||
F64(f64),
|
||||
F32(f32),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WhenBranch {
|
||||
pub patterns: PoolVec<Pattern2>, // 4B
|
||||
pub body: ExprId, // 3B
|
||||
pub guard: Option<ExprId>, // 4B
|
||||
}
|
||||
|
||||
/// This is overflow data from a Closure variant, which needs to store
|
||||
/// more than 32B of total data
|
||||
#[derive(Debug)]
|
||||
pub struct ClosureExtra {
|
||||
pub return_type: Variable, // 4B
|
||||
pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B
|
||||
pub closure_type: Variable, // 4B
|
||||
pub closure_ext_var: Variable, // 4B
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
use super::expr2::{Expr2, ExprId};
|
||||
use crate::{
|
||||
lang::core::{expr::record_field::RecordField, val_def::value_def_to_string},
|
||||
mem_pool::pool::Pool,
|
||||
};
|
||||
use roc_types::subs::Variable;
|
||||
use std::fmt::Write as _; // import without risk of name clashing
|
||||
|
||||
pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String {
|
||||
let mut full_string = String::new();
|
||||
let expr2 = pool.get(node_id);
|
||||
|
||||
expr2_to_string_helper(expr2, 0, pool, &mut full_string);
|
||||
|
||||
full_string
|
||||
}
|
||||
|
||||
fn get_spacing(indent_level: usize) -> String {
|
||||
std::iter::repeat(" ")
|
||||
.take(indent_level)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn expr2_to_string_helper(
|
||||
expr2: &Expr2,
|
||||
indent_level: usize,
|
||||
pool: &Pool,
|
||||
out_string: &mut String,
|
||||
) {
|
||||
out_string.push_str(&get_spacing(indent_level));
|
||||
|
||||
match expr2 {
|
||||
Expr2::SmallStr(arr_string) => {
|
||||
let _ = write!(out_string, "SmallStr(\"{}\")", arr_string.as_str());
|
||||
}
|
||||
Expr2::Str(pool_str) => {
|
||||
let _ = write!(out_string, "Str(\"{}\")", pool_str.as_str(pool));
|
||||
}
|
||||
Expr2::Blank => out_string.push_str("Blank"),
|
||||
Expr2::EmptyRecord => out_string.push_str("EmptyRecord"),
|
||||
Expr2::Record { record_var, fields } => {
|
||||
out_string.push_str("Record:\n");
|
||||
out_string.push_str(&var_to_string(record_var, indent_level + 1));
|
||||
|
||||
let _ = writeln!(out_string, "{}fields: [", get_spacing(indent_level + 1));
|
||||
|
||||
let mut first_child = true;
|
||||
|
||||
for field in fields.iter(pool) {
|
||||
if !first_child {
|
||||
out_string.push_str(", ")
|
||||
} else {
|
||||
first_child = false;
|
||||
}
|
||||
|
||||
match field {
|
||||
RecordField::InvalidLabelOnly(pool_str, var) => {
|
||||
let _ = write!(
|
||||
out_string,
|
||||
"{}({}, Var({:?})",
|
||||
get_spacing(indent_level + 2),
|
||||
pool_str.as_str(pool),
|
||||
var,
|
||||
);
|
||||
}
|
||||
RecordField::LabelOnly(pool_str, var, symbol) => {
|
||||
let _ = write!(
|
||||
out_string,
|
||||
"{}({}, Var({:?}), Symbol({:?})",
|
||||
get_spacing(indent_level + 2),
|
||||
pool_str.as_str(pool),
|
||||
var,
|
||||
symbol
|
||||
);
|
||||
}
|
||||
RecordField::LabeledValue(pool_str, var, val_node_id) => {
|
||||
let _ = writeln!(
|
||||
out_string,
|
||||
"{}({}, Var({:?}), Expr2(",
|
||||
get_spacing(indent_level + 2),
|
||||
pool_str.as_str(pool),
|
||||
var,
|
||||
);
|
||||
|
||||
let val_expr2 = pool.get(*val_node_id);
|
||||
expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string);
|
||||
let _ = writeln!(out_string, "{})", get_spacing(indent_level + 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(out_string, "{}]", get_spacing(indent_level + 1));
|
||||
}
|
||||
Expr2::List { elem_var, elems } => {
|
||||
out_string.push_str("List:\n");
|
||||
out_string.push_str(&var_to_string(elem_var, indent_level + 1));
|
||||
let _ = writeln!(out_string, "{}elems: [\n", get_spacing(indent_level + 1));
|
||||
|
||||
let mut first_elt = true;
|
||||
|
||||
for elem_expr2_id in elems.iter(pool) {
|
||||
if !first_elt {
|
||||
out_string.push_str(", ")
|
||||
} else {
|
||||
first_elt = false;
|
||||
}
|
||||
|
||||
let elem_expr2 = pool.get(*elem_expr2_id);
|
||||
|
||||
expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string)
|
||||
}
|
||||
|
||||
let _ = writeln!(out_string, "{}]", get_spacing(indent_level + 1));
|
||||
}
|
||||
Expr2::InvalidLookup(pool_str) => {
|
||||
let _ = write!(out_string, "InvalidLookup({})", pool_str.as_str(pool));
|
||||
}
|
||||
Expr2::SmallInt { text, .. } => {
|
||||
let _ = write!(out_string, "SmallInt({})", text.as_str(pool));
|
||||
}
|
||||
Expr2::LetValue {
|
||||
def_id, body_id, ..
|
||||
} => {
|
||||
let _ = write!(
|
||||
out_string,
|
||||
"LetValue(def_id: >>{:?}), body_id: >>{:?})",
|
||||
value_def_to_string(pool.get(*def_id), pool),
|
||||
pool.get(*body_id)
|
||||
);
|
||||
}
|
||||
Expr2::Call { .. } => {
|
||||
let _ = write!(out_string, "Call({expr2:?})");
|
||||
}
|
||||
Expr2::Closure { args, .. } => {
|
||||
out_string.push_str("Closure:\n");
|
||||
let _ = writeln!(out_string, "{}args: [", get_spacing(indent_level + 1));
|
||||
|
||||
for (_, pattern_id) in args.iter(pool) {
|
||||
let arg_pattern2 = pool.get(*pattern_id);
|
||||
|
||||
let _ = writeln!(
|
||||
out_string,
|
||||
"{}{:?}",
|
||||
get_spacing(indent_level + 2),
|
||||
arg_pattern2
|
||||
);
|
||||
}
|
||||
}
|
||||
&Expr2::Var { .. } => {
|
||||
let _ = write!(out_string, "{expr2:?}");
|
||||
}
|
||||
Expr2::RuntimeError { .. } => {
|
||||
out_string.push_str("RuntimeError\n");
|
||||
}
|
||||
other => todo!("Implement for {:?}", other),
|
||||
}
|
||||
|
||||
out_string.push('\n');
|
||||
}
|
||||
|
||||
fn var_to_string(some_var: &Variable, indent_level: usize) -> String {
|
||||
format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var)
|
||||
}
|
@ -1,702 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expr::{IntValue, Recursive};
|
||||
use roc_can::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
|
||||
};
|
||||
use roc_can::operator::desugar_expr;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ident::Accessor;
|
||||
use roc_parse::{ast::Expr, pattern::PatternType};
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
use super::{expr2::Expr2, output::Output};
|
||||
use crate::canonicalization::canonicalize::{
|
||||
canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem,
|
||||
};
|
||||
use crate::lang::core::declaration::decl_to_let;
|
||||
use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs};
|
||||
use crate::lang::core::expr::expr2::ClosureExtra;
|
||||
use crate::lang::core::pattern::to_pattern2;
|
||||
use crate::lang::core::str::flatten_str_literal;
|
||||
use crate::mem_pool::shallow_clone::ShallowClone;
|
||||
use crate::{
|
||||
lang::{
|
||||
core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal},
|
||||
env::Env,
|
||||
scope::Scope,
|
||||
},
|
||||
mem_pool::{pool_str::PoolStr, pool_vec::PoolVec},
|
||||
};
|
||||
|
||||
pub fn loc_expr_to_expr2<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: Loc<Expr<'a>>,
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
) -> (Expr2, Output) {
|
||||
let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr));
|
||||
|
||||
expr_to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region)
|
||||
}
|
||||
|
||||
const ZERO: Region = Region::zero();
|
||||
|
||||
pub fn expr_to_expr2<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
parse_expr: &'a roc_parse::ast::Expr<'a>,
|
||||
region: Region,
|
||||
) -> (Expr2, self::Output) {
|
||||
use roc_parse::ast::Expr::*;
|
||||
//dbg!("{:?}", parse_expr);
|
||||
|
||||
match parse_expr {
|
||||
Float(string) => {
|
||||
match finish_parsing_float(string) {
|
||||
Ok((string_without_suffix, float, _bound)) => {
|
||||
let expr = Expr2::Float {
|
||||
number: FloatVal::F64(float),
|
||||
var: env.var_store.fresh(),
|
||||
text: PoolStr::new(string_without_suffix, env.pool),
|
||||
};
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Err((raw, error)) => {
|
||||
// emit runtime error
|
||||
let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into());
|
||||
|
||||
env.problem(Problem::RuntimeError(runtime_error));
|
||||
//
|
||||
// Expr::RuntimeError(runtime_error)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Num(string) => {
|
||||
match finish_parsing_num(string) {
|
||||
Ok((
|
||||
parsed,
|
||||
ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _),
|
||||
)) => {
|
||||
let expr = Expr2::SmallInt {
|
||||
number: IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME
|
||||
}),
|
||||
var: env.var_store.fresh(),
|
||||
// TODO non-hardcode
|
||||
style: IntStyle::Decimal,
|
||||
text: PoolStr::new(parsed, env.pool),
|
||||
};
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Ok((parsed, ParsedNumResult::Float(float, _))) => {
|
||||
let expr = Expr2::Float {
|
||||
number: FloatVal::F64(float),
|
||||
var: env.var_store.fresh(),
|
||||
text: PoolStr::new(parsed, env.pool),
|
||||
};
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Err((raw, error)) => {
|
||||
// emit runtime error
|
||||
let runtime_error = RuntimeError::InvalidInt(
|
||||
error,
|
||||
roc_parse::ast::Base::Decimal,
|
||||
ZERO,
|
||||
raw.into(),
|
||||
);
|
||||
|
||||
env.problem(Problem::RuntimeError(runtime_error));
|
||||
//
|
||||
// Expr::RuntimeError(runtime_error)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => {
|
||||
match finish_parsing_base(string, *base, *is_negative) {
|
||||
Ok((int, _bound)) => {
|
||||
let expr = Expr2::SmallInt {
|
||||
number: IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME
|
||||
}),
|
||||
var: env.var_store.fresh(),
|
||||
// TODO non-hardcode
|
||||
style: IntStyle::from_base(*base),
|
||||
text: PoolStr::new(string, env.pool),
|
||||
};
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Err((raw, error)) => {
|
||||
// emit runtime error
|
||||
let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into());
|
||||
|
||||
env.problem(Problem::RuntimeError(runtime_error));
|
||||
//
|
||||
// Expr::RuntimeError(runtime_error)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Str(literal) => flatten_str_literal(env, scope, literal),
|
||||
|
||||
List(items) => {
|
||||
let mut output = Output::default();
|
||||
let output_ref = &mut output;
|
||||
|
||||
let elems: PoolVec<ExprId> = PoolVec::with_capacity(items.len() as u32, env.pool);
|
||||
|
||||
for (node_id, item) in elems.iter_node_ids().zip(items.iter()) {
|
||||
let (expr, sub_output) = expr_to_expr2(env, scope, &item.value, item.region);
|
||||
|
||||
output_ref.union(sub_output);
|
||||
|
||||
let expr_id = env.pool.add(expr);
|
||||
env.pool[node_id] = expr_id;
|
||||
}
|
||||
|
||||
let expr = Expr2::List {
|
||||
elem_var: env.var_store.fresh(),
|
||||
elems,
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
|
||||
Tag(tag) => {
|
||||
// a tag without any arguments
|
||||
(
|
||||
Expr2::Tag {
|
||||
name: PoolStr::new(tag, env.pool),
|
||||
variant_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
arguments: PoolVec::empty(env.pool),
|
||||
},
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update: loc_update,
|
||||
} => {
|
||||
let (can_update, update_out) =
|
||||
expr_to_expr2(env, scope, &loc_update.value, loc_update.region);
|
||||
|
||||
if let Expr2::Var(symbol) = &can_update {
|
||||
match canonicalize_fields(env, scope, fields.items) {
|
||||
Ok((can_fields, mut output)) => {
|
||||
output.references.union_mut(update_out.references);
|
||||
|
||||
let answer = Expr2::Update {
|
||||
record_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
symbol: *symbol,
|
||||
updates: can_fields,
|
||||
};
|
||||
|
||||
(answer, output)
|
||||
}
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name: _,
|
||||
field_region: _,
|
||||
record_region: _,
|
||||
}) => {
|
||||
// let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue {
|
||||
// field_name,
|
||||
// field_region,
|
||||
// record_region,
|
||||
// };
|
||||
//
|
||||
// env.problem(Problem::RuntimeError(runtime_error));
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only (optionally qualified) variables can be updated, not arbitrary expressions
|
||||
|
||||
// let error = roc_problem::can::RuntimeError::InvalidRecordUpdate {
|
||||
// region: can_update.region,
|
||||
// };
|
||||
//
|
||||
// let answer = Expr::RuntimeError(error.clone());
|
||||
//
|
||||
// env.problems.push(Problem::RuntimeError(error));
|
||||
//
|
||||
// (answer, Output::default())
|
||||
todo!("{:?}", &can_update)
|
||||
}
|
||||
}
|
||||
|
||||
Record(fields) => {
|
||||
if fields.is_empty() {
|
||||
(Expr2::EmptyRecord, Output::default())
|
||||
} else {
|
||||
match canonicalize_fields(env, scope, fields.items) {
|
||||
Ok((can_fields, output)) => (
|
||||
Expr2::Record {
|
||||
record_var: env.var_store.fresh(),
|
||||
fields: can_fields,
|
||||
},
|
||||
output,
|
||||
),
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name: _,
|
||||
field_region: _,
|
||||
record_region: _,
|
||||
}) => {
|
||||
// let runtime_error = RuntimeError::InvalidOptionalValue {
|
||||
// field_name,
|
||||
// field_region,
|
||||
// record_region,
|
||||
// };
|
||||
//
|
||||
// env.problem(runtime_error);
|
||||
// (
|
||||
// Expr::RuntimeError(
|
||||
// ),
|
||||
// Output::default(),
|
||||
//
|
||||
// )
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecordAccess(record_expr, field) => {
|
||||
// TODO
|
||||
let region = ZERO;
|
||||
let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region);
|
||||
|
||||
(
|
||||
Expr2::Access {
|
||||
record_var: env.var_store.fresh(),
|
||||
field_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
expr: record_expr_id,
|
||||
field: PoolStr::new(field, env.pool),
|
||||
},
|
||||
output,
|
||||
)
|
||||
}
|
||||
|
||||
AccessorFunction(Accessor::RecordField(field)) => (
|
||||
Expr2::Accessor {
|
||||
function_var: env.var_store.fresh(),
|
||||
record_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
closure_var: env.var_store.fresh(),
|
||||
field_var: env.var_store.fresh(),
|
||||
field: PoolStr::new(field, env.pool),
|
||||
},
|
||||
Output::default(),
|
||||
),
|
||||
|
||||
If(branches, final_else) => {
|
||||
let mut new_branches = Vec::with_capacity(branches.len());
|
||||
let mut output = Output::default();
|
||||
|
||||
for (condition, then_branch) in branches.iter() {
|
||||
let (cond, cond_output) =
|
||||
expr_to_expr2(env, scope, &condition.value, condition.region);
|
||||
|
||||
let (then_expr, then_output) =
|
||||
expr_to_expr2(env, scope, &then_branch.value, then_branch.region);
|
||||
|
||||
output.references.union_mut(cond_output.references);
|
||||
output.references.union_mut(then_output.references);
|
||||
|
||||
new_branches.push((env.pool.add(cond), env.pool.add(then_expr)));
|
||||
}
|
||||
|
||||
let (else_expr, else_output) =
|
||||
expr_to_expr2(env, scope, &final_else.value, final_else.region);
|
||||
|
||||
output.references.union_mut(else_output.references);
|
||||
|
||||
let expr = Expr2::If {
|
||||
cond_var: env.var_store.fresh(),
|
||||
expr_var: env.var_store.fresh(),
|
||||
branches: PoolVec::new(new_branches.into_iter(), env.pool),
|
||||
final_else: env.pool.add(else_expr),
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
|
||||
When(loc_cond, branches) => {
|
||||
// Infer the condition expression's type.
|
||||
let cond_var = env.var_store.fresh();
|
||||
let (can_cond, mut output) =
|
||||
expr_to_expr2(env, scope, &loc_cond.value, loc_cond.region);
|
||||
|
||||
// the condition can never be a tail-call
|
||||
output.tail_call = None;
|
||||
|
||||
let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool);
|
||||
|
||||
for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) {
|
||||
let (can_when_branch, branch_references) =
|
||||
canonicalize_when_branch(env, scope, branch, &mut output);
|
||||
|
||||
output.references.union_mut(branch_references);
|
||||
|
||||
env.pool[node_id] = can_when_branch;
|
||||
}
|
||||
|
||||
// A "when" with no branches is a runtime error, but it will mess things up
|
||||
// if code gen mistakenly thinks this is a tail call just because its condition
|
||||
// happened to be one. (The condition gave us our initial output value.)
|
||||
if branches.is_empty() {
|
||||
output.tail_call = None;
|
||||
}
|
||||
|
||||
// Incorporate all three expressions into a combined Output value.
|
||||
let expr = Expr2::When {
|
||||
expr_var: env.var_store.fresh(),
|
||||
cond_var,
|
||||
cond: env.pool.add(can_cond),
|
||||
branches: can_branches,
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
|
||||
Closure(loc_arg_patterns, loc_body_expr) => {
|
||||
// The globally unique symbol that will refer to this closure once it gets converted
|
||||
// into a top-level procedure for code gen.
|
||||
//
|
||||
// In the Foo module, this will look something like Foo.$1 or Foo.$2.
|
||||
let (symbol, is_anonymous) = match env.closure_name_symbol {
|
||||
Some(symbol) => (symbol, false),
|
||||
None => (env.gen_unique_symbol(), true),
|
||||
};
|
||||
env.closure_name_symbol = None;
|
||||
|
||||
// The body expression gets a new scope for canonicalization.
|
||||
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
||||
// rest of this block, but keep the original around for later diffing.
|
||||
let original_scope = scope;
|
||||
let mut scope = original_scope.shallow_clone();
|
||||
let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool);
|
||||
let mut output = Output::default();
|
||||
|
||||
let mut bound_by_argument_patterns = MutSet::default();
|
||||
|
||||
for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) {
|
||||
let (new_output, can_arg) = to_pattern2(
|
||||
env,
|
||||
&mut scope,
|
||||
roc_parse::pattern::PatternType::FunctionArg,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
);
|
||||
|
||||
bound_by_argument_patterns
|
||||
.extend(new_output.references.bound_symbols.iter().copied());
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
let pattern_id = env.add(can_arg, loc_pattern.region);
|
||||
env.pool[node_id] = (env.var_store.fresh(), pattern_id);
|
||||
}
|
||||
|
||||
let (body_expr, new_output) =
|
||||
expr_to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region);
|
||||
|
||||
let mut captured_symbols: MutSet<Symbol> =
|
||||
new_output.references.lookups.iter().copied().collect();
|
||||
|
||||
// filter out the closure's name itself
|
||||
captured_symbols.remove(&symbol);
|
||||
|
||||
// symbols bound either in this pattern or deeper down are not captured!
|
||||
captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s));
|
||||
captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s));
|
||||
|
||||
// filter out top-level symbols
|
||||
// those will be globally available, and don't need to be captured
|
||||
captured_symbols.retain(|s| !env.top_level_symbols.contains(s));
|
||||
|
||||
// filter out imported symbols
|
||||
// those will be globally available, and don't need to be captured
|
||||
captured_symbols.retain(|s| s.module_id() == env.home);
|
||||
|
||||
// TODO any Closure that has an empty `captured_symbols` list could be excluded!
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
// filter out aliases
|
||||
captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s));
|
||||
|
||||
// filter out functions that don't close over anything
|
||||
captured_symbols.retain(|s| !output.non_closures.contains(s));
|
||||
|
||||
// Now that we've collected all the references, check to see if any of the args we defined
|
||||
// went unreferenced. If any did, report them as unused arguments.
|
||||
for (sub_symbol, region) in scope.symbols() {
|
||||
if !original_scope.contains_symbol(sub_symbol) {
|
||||
if !output.references.has_lookup(sub_symbol) {
|
||||
// The body never referenced this argument we declared. It's an unused argument!
|
||||
env.problem(Problem::UnusedArgument(
|
||||
symbol,
|
||||
is_anonymous,
|
||||
sub_symbol,
|
||||
region,
|
||||
));
|
||||
}
|
||||
|
||||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistent) local variable x!
|
||||
output.references.lookups.remove(&sub_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
env.register_closure(symbol, output.references.clone());
|
||||
|
||||
let mut captured_symbols: Vec<_> = captured_symbols
|
||||
.into_iter()
|
||||
.map(|s| (s, env.var_store.fresh()))
|
||||
.collect();
|
||||
|
||||
// sort symbols, so we know the order in which they're stored in the closure record
|
||||
captured_symbols.sort();
|
||||
|
||||
// store that this function doesn't capture anything. It will be promoted to a
|
||||
// top-level function, and does not need to be captured by other surrounding functions.
|
||||
if captured_symbols.is_empty() {
|
||||
output.non_closures.insert(symbol);
|
||||
}
|
||||
|
||||
let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool);
|
||||
|
||||
let extra = ClosureExtra {
|
||||
return_type: env.var_store.fresh(), // 4B
|
||||
captured_symbols, // 8B
|
||||
closure_type: env.var_store.fresh(), // 4B
|
||||
closure_ext_var: env.var_store.fresh(), // 4B
|
||||
};
|
||||
|
||||
(
|
||||
Expr2::Closure {
|
||||
function_type: env.var_store.fresh(),
|
||||
uniq_symbol: symbol,
|
||||
recursive: Recursive::NotRecursive,
|
||||
args: can_args,
|
||||
body_id: env.add(body_expr, loc_body_expr.region),
|
||||
extra: env.pool.add(extra),
|
||||
},
|
||||
output,
|
||||
)
|
||||
}
|
||||
|
||||
Apply(loc_fn, loc_args, application_style) => {
|
||||
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||
// (foo) bar baz
|
||||
let fn_region = loc_fn.region;
|
||||
|
||||
// Canonicalize the function expression and its arguments
|
||||
let (fn_expr, mut output) = expr_to_expr2(env, scope, &loc_fn.value, fn_region);
|
||||
// The function's return type
|
||||
let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool);
|
||||
|
||||
for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) {
|
||||
let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region);
|
||||
|
||||
env.pool[node_id] = (env.var_store.fresh(), arg_expr_id);
|
||||
|
||||
output.references.union_mut(arg_out.references);
|
||||
}
|
||||
|
||||
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
|
||||
output.tail_call = None;
|
||||
|
||||
let expr = match fn_expr {
|
||||
Expr2::Var(ref symbol) => {
|
||||
output.references.calls.insert(*symbol);
|
||||
|
||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||
output.tail_call = match &env.tailcallable_symbol {
|
||||
Some(tc_sym) if *tc_sym == *symbol => Some(*symbol),
|
||||
Some(_) | None => None,
|
||||
};
|
||||
|
||||
// IDEA: Expr2::CallByName?
|
||||
let fn_expr_id = env.add(fn_expr, fn_region);
|
||||
Expr2::Call {
|
||||
args,
|
||||
expr_id: fn_expr_id,
|
||||
expr_var: env.var_store.fresh(),
|
||||
fn_var: env.var_store.fresh(),
|
||||
closure_var: env.var_store.fresh(),
|
||||
called_via: *application_style,
|
||||
}
|
||||
}
|
||||
Expr2::RuntimeError() => {
|
||||
// We can't call a runtime error; bail out by propagating it!
|
||||
return (fn_expr, output);
|
||||
}
|
||||
Expr2::Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
..
|
||||
} => Expr2::Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
_ => {
|
||||
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
||||
let fn_expr_id = env.add(fn_expr, fn_region);
|
||||
Expr2::Call {
|
||||
args,
|
||||
expr_id: fn_expr_id,
|
||||
expr_var: env.var_store.fresh(),
|
||||
fn_var: env.var_store.fresh(),
|
||||
closure_var: env.var_store.fresh(),
|
||||
called_via: *application_style,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
|
||||
Defs(loc_defs, loc_ret) => {
|
||||
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
|
||||
env,
|
||||
Output::default(),
|
||||
scope,
|
||||
loc_defs,
|
||||
PatternType::DefExpr,
|
||||
);
|
||||
|
||||
// The def as a whole is a tail call iff its return expression is a tail call.
|
||||
// Use its output as a starting point because its tail_call already has the right answer!
|
||||
let (ret_expr, mut output) =
|
||||
expr_to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region);
|
||||
|
||||
output
|
||||
.introduced_variables
|
||||
.union(&defs_output.introduced_variables);
|
||||
|
||||
output.references.union_mut(defs_output.references);
|
||||
|
||||
// Now that we've collected all the references, check to see if any of the new idents
|
||||
// we defined went unused by the return expression. If any were unused, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
if !output.references.has_lookup(symbol) {
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
}
|
||||
|
||||
let (can_defs, output) = sort_can_defs(env, unsorted, output);
|
||||
|
||||
match can_defs {
|
||||
Ok(decls) => {
|
||||
let mut expr = ret_expr;
|
||||
|
||||
for declaration in decls.into_iter().rev() {
|
||||
expr = decl_to_let(env.pool, env.var_store, declaration, expr);
|
||||
}
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
Err(_err) => {
|
||||
// TODO: fix this to be something from Expr2
|
||||
// (RuntimeError(err), output)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrecedenceConflict { .. } => {
|
||||
// use roc_problem::can::RuntimeError::*;
|
||||
//
|
||||
// let problem = PrecedenceProblem::BothNonAssociative(
|
||||
// *whole_region,
|
||||
// binop1.clone(),
|
||||
// binop2.clone(),
|
||||
// );
|
||||
//
|
||||
// env.problem(Problem::PrecedenceProblem(problem.clone()));
|
||||
//
|
||||
// (
|
||||
// RuntimeError(InvalidPrecedence(problem, region)),
|
||||
// Output::default(),
|
||||
// )
|
||||
todo!()
|
||||
}
|
||||
MalformedClosure => {
|
||||
// use roc_problem::can::RuntimeError::*;
|
||||
// (RuntimeError(MalformedClosure(region)), Output::default())
|
||||
todo!()
|
||||
}
|
||||
MalformedIdent(_name, _problem) => {
|
||||
// use roc_problem::can::RuntimeError::*;
|
||||
//
|
||||
// let problem = MalformedIdentifier((*name).into(), region);
|
||||
// env.problem(Problem::RuntimeError(problem.clone()));
|
||||
//
|
||||
// (RuntimeError(problem), Output::default())
|
||||
todo!()
|
||||
}
|
||||
Var {
|
||||
module_name, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar`
|
||||
ident,
|
||||
} => canonicalize_lookup(env, scope, module_name, ident, region),
|
||||
|
||||
ParensAround(sub_expr) => expr_to_expr2(env, scope, sub_expr, region),
|
||||
|
||||
// Below this point, we shouln't see any of these nodes anymore because
|
||||
// operator desugaring should have removed them!
|
||||
bad_expr @ SpaceBefore(_, _) => {
|
||||
panic!(
|
||||
"A SpaceBefore did not get removed during operator desugaring somehow: {bad_expr:#?}"
|
||||
);
|
||||
}
|
||||
bad_expr @ SpaceAfter(_, _) => {
|
||||
panic!(
|
||||
"A SpaceAfter did not get removed during operator desugaring somehow: {bad_expr:#?}"
|
||||
);
|
||||
}
|
||||
bad_expr @ BinOps { .. } => {
|
||||
panic!("A binary operator chain did not get desugared somehow: {bad_expr:#?}");
|
||||
}
|
||||
bad_expr @ UnaryOp(_, _) => {
|
||||
panic!("A unary operator did not get desugared somehow: {bad_expr:#?}");
|
||||
}
|
||||
|
||||
rest => todo!("not yet implemented {:?}", rest),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_expr_id<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
parse_expr: &'a roc_parse::ast::Expr<'a>,
|
||||
region: Region,
|
||||
) -> (ExprId, Output) {
|
||||
let (expr, output) = expr_to_expr2(env, scope, parse_expr, region);
|
||||
|
||||
(env.add(expr, region), output)
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct IntroducedVariables {
|
||||
// Rigids must be unique within a type annotation.
|
||||
// E.g. in `identity : a -> a`, there should only be one
|
||||
// variable (a rigid one, with name "a").
|
||||
// Hence `rigids : Map<Lowercase, Variable>`
|
||||
//
|
||||
// But then between annotations, the same name can occur multiple times,
|
||||
// but a variable can only have one name. Therefore
|
||||
// `ftv : Map<Variable, Lowercase>`.
|
||||
pub wildcards: Vec<Variable>,
|
||||
pub var_by_name: MutMap<Lowercase, Variable>,
|
||||
pub name_by_var: MutMap<Variable, Lowercase>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
impl IntroducedVariables {
|
||||
pub fn insert_named(&mut self, name: Lowercase, var: Variable) {
|
||||
self.var_by_name.insert(name.clone(), var);
|
||||
self.name_by_var.insert(var, name);
|
||||
}
|
||||
|
||||
pub fn insert_wildcard(&mut self, var: Variable) {
|
||||
self.wildcards.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
|
||||
self.host_exposed_aliases.insert(symbol, var);
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: &Self) {
|
||||
self.wildcards.extend(other.wildcards.iter().cloned());
|
||||
self.var_by_name.extend(other.var_by_name.clone());
|
||||
self.name_by_var.extend(other.name_by_var.clone());
|
||||
self.host_exposed_aliases
|
||||
.extend(other.host_exposed_aliases.clone());
|
||||
}
|
||||
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
|
||||
self.var_by_name.get(name)
|
||||
}
|
||||
|
||||
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
|
||||
self.name_by_var.get(&var)
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
pub mod expr2;
|
||||
pub mod expr2_to_string;
|
||||
pub mod expr_to_expr2;
|
||||
mod introduced_vars;
|
||||
pub(crate) mod output;
|
||||
pub mod record_field;
|
@ -1,30 +0,0 @@
|
||||
use crate::{
|
||||
lang::core::{def::def::References, types::Alias},
|
||||
mem_pool::pool::NodeId,
|
||||
};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use super::introduced_vars::IntroducedVariables;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
pub struct Output {
|
||||
pub references: References,
|
||||
pub tail_call: Option<Symbol>,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub aliases: MutMap<Symbol, NodeId<Alias>>,
|
||||
pub non_closures: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn union(&mut self, other: Self) {
|
||||
self.references.union_mut(other.references);
|
||||
|
||||
if let (None, Some(later)) = (self.tail_call, other.tail_call) {
|
||||
self.tail_call = Some(later);
|
||||
}
|
||||
|
||||
self.aliases.extend(other.aliases);
|
||||
self.non_closures.extend(other.non_closures);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use crate::mem_pool::pool_str::PoolStr;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use super::expr2::ExprId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RecordField {
|
||||
InvalidLabelOnly(PoolStr, Variable),
|
||||
LabelOnly(PoolStr, Variable, Symbol),
|
||||
LabeledValue(PoolStr, Variable, ExprId),
|
||||
}
|
||||
|
||||
use RecordField::*;
|
||||
|
||||
impl RecordField {
|
||||
pub fn get_record_field_var(&self) -> &Variable {
|
||||
match self {
|
||||
InvalidLabelOnly(_, var) => var,
|
||||
LabelOnly(_, var, _) => var,
|
||||
LabeledValue(_, var, _) => var,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_record_field_pool_str(&self) -> &PoolStr {
|
||||
match self {
|
||||
InvalidLabelOnly(pool_str, _) => pool_str,
|
||||
LabelOnly(pool_str, _, _) => pool_str,
|
||||
LabeledValue(pool_str, _, _) => pool_str,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr {
|
||||
match self {
|
||||
InvalidLabelOnly(pool_str, _) => pool_str,
|
||||
LabelOnly(pool_str, _, _) => pool_str,
|
||||
LabeledValue(pool_str, _, _) => pool_str,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_record_field_val_node_id(&self) -> Option<ExprId> {
|
||||
match self {
|
||||
InvalidLabelOnly(_, _) => None,
|
||||
LabelOnly(_, _, _) => None,
|
||||
LabeledValue(_, _, field_val_id) => Some(*field_val_id),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use crate::{
|
||||
lang::rigids::Rigids,
|
||||
mem_pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone},
|
||||
};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use super::{
|
||||
expr::expr2::ExprId,
|
||||
pattern::PatternId,
|
||||
types::{Type2, TypeId},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FunctionDef {
|
||||
WithAnnotation {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(NodeId<Type2>, PatternId)>, // 8B
|
||||
rigids: NodeId<Rigids>, // 4B
|
||||
return_type: TypeId, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
NoAnnotation {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(Variable, PatternId)>, // 8B
|
||||
return_var: Variable, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
}
|
||||
|
||||
impl ShallowClone for FunctionDef {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
match self {
|
||||
Self::WithAnnotation {
|
||||
name,
|
||||
arguments,
|
||||
rigids,
|
||||
return_type,
|
||||
body_id,
|
||||
} => Self::WithAnnotation {
|
||||
name: *name,
|
||||
arguments: arguments.shallow_clone(),
|
||||
rigids: *rigids,
|
||||
return_type: *return_type,
|
||||
body_id: *body_id,
|
||||
},
|
||||
|
||||
Self::NoAnnotation {
|
||||
name,
|
||||
arguments,
|
||||
return_var,
|
||||
body_id,
|
||||
} => Self::NoAnnotation {
|
||||
name: *name,
|
||||
arguments: arguments.shallow_clone(),
|
||||
return_var: *return_var,
|
||||
body_id: *body_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
use super::expr::expr2::ExprId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppHeader {
|
||||
pub app_name: String,
|
||||
pub packages_base: String,
|
||||
pub imports: Vec<String>,
|
||||
pub provides: Vec<String>,
|
||||
pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
pub mod ast;
|
||||
mod declaration;
|
||||
pub mod def;
|
||||
pub mod expr;
|
||||
pub mod fun_def;
|
||||
pub mod header;
|
||||
pub mod pattern;
|
||||
pub mod str;
|
||||
pub mod types;
|
||||
pub mod val_def;
|
@ -1,643 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use bumpalo::collections::Vec as BumpVec;
|
||||
use roc_can::expr::IntValue;
|
||||
use roc_can::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
|
||||
};
|
||||
use roc_collections::all::BumpMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use crate::ast_error::{ASTResult, UnexpectedPattern2VariantSnafu};
|
||||
use crate::constrain::Constraint;
|
||||
use crate::lang::core::expr::expr_to_expr2::to_expr_id;
|
||||
use crate::lang::env::Env;
|
||||
use crate::lang::scope::Scope;
|
||||
use crate::mem_pool::pool::{NodeId, Pool};
|
||||
use crate::mem_pool::pool_str::PoolStr;
|
||||
use crate::mem_pool::pool_vec::PoolVec;
|
||||
use crate::mem_pool::shallow_clone::ShallowClone;
|
||||
|
||||
use super::expr::expr2::{ExprId, FloatVal, IntVal};
|
||||
use super::expr::output::Output;
|
||||
use super::types::Type2;
|
||||
|
||||
pub type PatternId = NodeId<Pattern2>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Pattern2 {
|
||||
Identifier(Symbol), // 8B
|
||||
NumLiteral(Variable, i64), // 4B + 8B
|
||||
IntLiteral(IntVal), // 16B
|
||||
FloatLiteral(FloatVal), // 16B
|
||||
StrLiteral(PoolStr), // 8B
|
||||
CharacterLiteral(char), // 4B
|
||||
Underscore, // 0B
|
||||
Tag {
|
||||
whole_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
tag_name: PoolStr, // 8B
|
||||
arguments: PoolVec<(Variable, PatternId)>, // 8B
|
||||
},
|
||||
RecordDestructure {
|
||||
whole_var: Variable, // 4B
|
||||
ext_var: Variable, // 4B
|
||||
destructs: PoolVec<RecordDestruct>, // 8B
|
||||
},
|
||||
|
||||
// Runtime Exceptions
|
||||
// TODO: figure out how to better handle regions
|
||||
// to keep this member under 32. With 2 Regions
|
||||
// it ends up at size 40
|
||||
Shadowed {
|
||||
shadowed_ident: PoolStr,
|
||||
// definition: Region,
|
||||
// shadowed_at: Region,
|
||||
},
|
||||
|
||||
/// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(Region),
|
||||
// parse error patterns
|
||||
MalformedPattern(MalformedPatternProblem, Region),
|
||||
}
|
||||
|
||||
impl ShallowClone for Pattern2 {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PatternState2<'a> {
|
||||
pub headers: BumpMap<Symbol, Type2>,
|
||||
pub vars: BumpVec<'a, Variable>,
|
||||
pub constraints: BumpVec<'a, Constraint<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RecordDestruct {
|
||||
pub var: Variable, // 4B
|
||||
pub label: PoolStr, // 8B
|
||||
pub symbol: Symbol, // 8B
|
||||
pub typ: NodeId<DestructType>, // 4B
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DestructType {
|
||||
Required,
|
||||
Optional(Variable, ExprId), // 4B + 4B
|
||||
Guard(Variable, PatternId), // 4B + 4B
|
||||
}
|
||||
|
||||
pub fn as_pattern_id<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
pattern_id: PatternId,
|
||||
pattern_type: PatternType,
|
||||
pattern: &roc_parse::ast::Pattern<'a>,
|
||||
region: Region,
|
||||
) -> Output {
|
||||
let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region);
|
||||
|
||||
env.pool[pattern_id] = can_pattern;
|
||||
env.set_region(pattern_id, region);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn to_pattern_id<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
pattern_type: PatternType,
|
||||
pattern: &roc_parse::ast::Pattern<'a>,
|
||||
region: Region,
|
||||
) -> (Output, PatternId) {
|
||||
let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region);
|
||||
|
||||
let pattern_id = env.pool.add(can_pattern);
|
||||
env.set_region(pattern_id, region);
|
||||
|
||||
(output, pattern_id)
|
||||
}
|
||||
|
||||
pub fn to_pattern2<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
pattern_type: PatternType,
|
||||
pattern: &roc_parse::ast::Pattern<'a>,
|
||||
region: Region,
|
||||
) -> (Output, Pattern2) {
|
||||
use roc_parse::ast::Pattern::*;
|
||||
use PatternType::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
let can_pattern = match pattern {
|
||||
Identifier(name) => match scope.introduce(
|
||||
(*name).into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
) {
|
||||
Ok(symbol) => {
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
|
||||
Pattern2::Identifier(symbol)
|
||||
}
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
let name: &str = shadow.value.as_ref();
|
||||
|
||||
Pattern2::Shadowed {
|
||||
shadowed_ident: PoolStr::new(name, env.pool),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
QualifiedIdentifier { .. } => {
|
||||
let problem = MalformedPatternProblem::QualifiedIdentifier;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
|
||||
Underscore(_) => Pattern2::Underscore,
|
||||
|
||||
FloatLiteral(ref string) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_float(string) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedFloat;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok((_, float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
NumLiteral(string) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_num(string) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok((_, ParsedNumResult::UnknownNum(int, _bound))) => {
|
||||
Pattern2::NumLiteral(
|
||||
env.var_store.fresh(),
|
||||
match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME
|
||||
},
|
||||
)
|
||||
}
|
||||
Ok((_, ParsedNumResult::Int(int, _bound))) => {
|
||||
Pattern2::IntLiteral(IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME
|
||||
}))
|
||||
}
|
||||
Ok((_, ParsedNumResult::Float(int, _bound))) => {
|
||||
Pattern2::FloatLiteral(FloatVal::F64(int))
|
||||
}
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => match pattern_type {
|
||||
WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedBase(*base);
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok((int, _bound)) => {
|
||||
let int = match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME
|
||||
};
|
||||
if *is_negative {
|
||||
Pattern2::IntLiteral(IntVal::I64(-int))
|
||||
} else {
|
||||
Pattern2::IntLiteral(IntVal::I64(int))
|
||||
}
|
||||
}
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
StrLiteral(literal) => match pattern_type {
|
||||
WhenBranch => flatten_str_literal(env.pool, literal),
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
SingleQuote(string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
let mut it = string.chars().peekable();
|
||||
if let Some(char) = it.next() {
|
||||
if it.peek().is_none() {
|
||||
Pattern2::CharacterLiteral(char)
|
||||
} else {
|
||||
// multiple chars is found
|
||||
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
} else {
|
||||
// no characters found
|
||||
let problem = MalformedPatternProblem::EmptySingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
}
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
Tag(name) => {
|
||||
// Canonicalize the tag's name.
|
||||
Pattern2::Tag {
|
||||
whole_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
tag_name: PoolStr::new(name, env.pool),
|
||||
arguments: PoolVec::empty(env.pool),
|
||||
}
|
||||
}
|
||||
|
||||
OpaqueRef(..) => internal_error!("opaques not implemented"),
|
||||
|
||||
Apply(tag, patterns) => {
|
||||
let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool);
|
||||
for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) {
|
||||
let (new_output, can_pattern) = to_pattern2(
|
||||
env,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
);
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
let can_pattern_id = env.pool.add(can_pattern);
|
||||
|
||||
env.pool[node_id] = (env.var_store.fresh(), can_pattern_id);
|
||||
}
|
||||
|
||||
match tag.value {
|
||||
Tag(name) => Pattern2::Tag {
|
||||
whole_var: env.var_store.fresh(),
|
||||
ext_var: env.var_store.fresh(),
|
||||
tag_name: PoolStr::new(name, env.pool),
|
||||
arguments: can_patterns,
|
||||
},
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
}
|
||||
}
|
||||
|
||||
RecordDestructure(patterns) => {
|
||||
let ext_var = env.var_store.fresh();
|
||||
let whole_var = env.var_store.fresh();
|
||||
let destructs = PoolVec::with_capacity(patterns.len() as u32, env.pool);
|
||||
let opt_erroneous = None;
|
||||
|
||||
for (node_id, loc_pattern) in destructs.iter_node_ids().zip((*patterns).iter()) {
|
||||
match loc_pattern.value {
|
||||
Identifier(label) => {
|
||||
match scope.introduce(
|
||||
label.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
) {
|
||||
Ok(symbol) => {
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
|
||||
let destruct = RecordDestruct {
|
||||
var: env.var_store.fresh(),
|
||||
label: PoolStr::new(label, env.pool),
|
||||
symbol,
|
||||
typ: env.pool.add(DestructType::Required),
|
||||
};
|
||||
|
||||
env.pool[node_id] = destruct;
|
||||
env.set_region(node_id, loc_pattern.region);
|
||||
}
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// let shadowed = Pattern2::Shadowed {
|
||||
// definition: original_region,
|
||||
// shadowed_at: loc_pattern.region,
|
||||
// shadowed_ident: shadow.value,
|
||||
// };
|
||||
|
||||
// No matter what the other patterns
|
||||
// are, we're definitely shadowed and will
|
||||
// get a runtime exception as soon as we
|
||||
// encounter the first bad pattern.
|
||||
// opt_erroneous = Some();
|
||||
// env.pool[node_id] = sha;
|
||||
// env.set_region(node_id, loc_pattern.region);
|
||||
todo!("we must both report/store the problem, but also not lose any information")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
RequiredField(label, loc_guard) => {
|
||||
// a guard does not introduce the label into scope!
|
||||
let symbol = scope.ignore(label.into(), &mut env.ident_ids);
|
||||
let (new_output, can_guard) = to_pattern_id(
|
||||
env,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_guard.value,
|
||||
loc_guard.region,
|
||||
);
|
||||
|
||||
let destruct = RecordDestruct {
|
||||
var: env.var_store.fresh(),
|
||||
label: PoolStr::new(label, env.pool),
|
||||
symbol,
|
||||
typ: env
|
||||
.pool
|
||||
.add(DestructType::Guard(env.var_store.fresh(), can_guard)),
|
||||
};
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
env.pool[node_id] = destruct;
|
||||
env.set_region(node_id, loc_pattern.region);
|
||||
}
|
||||
OptionalField(label, loc_default) => {
|
||||
// an optional DOES introduce the label into scope!
|
||||
match scope.introduce(
|
||||
label.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
) {
|
||||
Ok(symbol) => {
|
||||
let (can_default, expr_output) =
|
||||
to_expr_id(env, scope, &loc_default.value, loc_default.region);
|
||||
|
||||
// an optional field binds the symbol!
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
|
||||
output.union(expr_output);
|
||||
|
||||
let destruct = RecordDestruct {
|
||||
var: env.var_store.fresh(),
|
||||
label: PoolStr::new(label, env.pool),
|
||||
symbol,
|
||||
typ: env.pool.add(DestructType::Optional(
|
||||
env.var_store.fresh(),
|
||||
can_default,
|
||||
)),
|
||||
};
|
||||
|
||||
env.pool[node_id] = destruct;
|
||||
env.set_region(node_id, loc_pattern.region);
|
||||
}
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
// are, we're definitely shadowed and will
|
||||
// get a runtime exception as soon as we
|
||||
// encounter the first bad pattern.
|
||||
// opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
||||
todo!("must report problem but also not loose any information")
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => unreachable!("Any other pattern should have given a parse error"),
|
||||
}
|
||||
}
|
||||
|
||||
// If we encountered an erroneous pattern (e.g. one with shadowing),
|
||||
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
|
||||
opt_erroneous.unwrap_or(Pattern2::RecordDestructure {
|
||||
whole_var,
|
||||
ext_var,
|
||||
destructs,
|
||||
})
|
||||
}
|
||||
|
||||
RequiredField(_name, _loc_pattern) => {
|
||||
unreachable!("should have been handled in RecordDestructure");
|
||||
}
|
||||
OptionalField(_name, _loc_pattern) => {
|
||||
unreachable!("should have been handled in RecordDestructure");
|
||||
}
|
||||
|
||||
Tuple(..) => todo!(),
|
||||
List(..) => todo!(),
|
||||
ListRest(_) => todo!(),
|
||||
As(_, _) => todo!(),
|
||||
|
||||
Malformed(_str) => {
|
||||
let problem = MalformedPatternProblem::Unknown;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
|
||||
MalformedIdent(_str, bad_ident) => {
|
||||
let problem = MalformedPatternProblem::BadIdent(*bad_ident);
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
|
||||
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
|
||||
return to_pattern2(env, scope, pattern_type, sub_pattern, region)
|
||||
}
|
||||
};
|
||||
|
||||
(output, can_pattern)
|
||||
}
|
||||
|
||||
pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
|
||||
use Pattern2::*;
|
||||
let mut symbols = Vec::new();
|
||||
let mut stack = vec![initial];
|
||||
|
||||
while let Some(pattern) = stack.pop() {
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
symbols.push(*symbol);
|
||||
}
|
||||
|
||||
Tag { arguments, .. } => {
|
||||
for (_, pat_id) in arguments.iter(pool) {
|
||||
let pat = pool.get(*pat_id);
|
||||
stack.push(pat);
|
||||
}
|
||||
}
|
||||
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for destruct in destructs.iter(pool) {
|
||||
let destruct_type = pool.get(destruct.typ);
|
||||
|
||||
if let DestructType::Guard(_, subpattern_id) = &destruct_type {
|
||||
let subpattern = pool.get(*subpattern_id);
|
||||
stack.push(subpattern);
|
||||
} else {
|
||||
symbols.push(destruct.symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumLiteral(_, _)
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
| UnsupportedPattern(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
symbols
|
||||
}
|
||||
|
||||
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult<String> {
|
||||
match pattern {
|
||||
Pattern2::Identifier(symbol) => Ok(symbol.as_str(interns).to_string()),
|
||||
other => UnexpectedPattern2VariantSnafu {
|
||||
required_pattern2: "Identifier".to_string(),
|
||||
encountered_pattern2: format!("{:?}", other),
|
||||
}
|
||||
.fail()?,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbols_and_variables_from_pattern(
|
||||
pool: &Pool,
|
||||
initial: &Pattern2,
|
||||
initial_var: Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
use Pattern2::*;
|
||||
let mut symbols = Vec::new();
|
||||
let mut stack = vec![(initial_var, initial)];
|
||||
|
||||
while let Some((variable, pattern)) = stack.pop() {
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
symbols.push((*symbol, variable));
|
||||
}
|
||||
|
||||
Tag { arguments, .. } => {
|
||||
for (var, pat_id) in arguments.iter(pool) {
|
||||
let pat = pool.get(*pat_id);
|
||||
stack.push((*var, pat));
|
||||
}
|
||||
}
|
||||
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for destruct in destructs.iter(pool) {
|
||||
let destruct_type = pool.get(destruct.typ);
|
||||
|
||||
if let DestructType::Guard(_, subpattern_id) = &destruct_type {
|
||||
let subpattern = pool.get(*subpattern_id);
|
||||
stack.push((destruct.var, subpattern));
|
||||
} else {
|
||||
symbols.push((destruct.symbol, destruct.var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumLiteral(_, _)
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
| UnsupportedPattern(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
symbols
|
||||
}
|
||||
|
||||
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
||||
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
|
||||
fn unsupported_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
pattern_type: PatternType,
|
||||
region: Region,
|
||||
) -> Pattern2 {
|
||||
use roc_problem::can::BadPattern;
|
||||
env.problem(Problem::UnsupportedPattern(
|
||||
BadPattern::Unsupported(pattern_type),
|
||||
region,
|
||||
));
|
||||
|
||||
Pattern2::UnsupportedPattern(region)
|
||||
}
|
||||
|
||||
pub(crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 {
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
|
||||
match literal {
|
||||
PlainLine(str_slice) => Pattern2::StrLiteral(PoolStr::new(str_slice, pool)),
|
||||
Line(segments) => flatten_str_lines(pool, &[segments]),
|
||||
Block(lines) => flatten_str_lines(pool, lines),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 {
|
||||
use StrSegment::*;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
for line in lines {
|
||||
for segment in line.iter() {
|
||||
match segment {
|
||||
Plaintext(string) => {
|
||||
buf.push_str(string);
|
||||
}
|
||||
Unicode(loc_digits) => {
|
||||
todo!("parse unicode digits {:?}", loc_digits);
|
||||
}
|
||||
Interpolated(loc_expr) => {
|
||||
return Pattern2::UnsupportedPattern(loc_expr.region);
|
||||
}
|
||||
EscapedChar(escaped) => buf.push(escaped.unescape()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pattern2::StrLiteral(PoolStr::new(&buf, pool))
|
||||
}
|
||||
|
||||
/// When we detect a malformed pattern like `3.X` or `0b5`,
|
||||
/// report it to Env and return an UnsupportedPattern runtime error pattern.
|
||||
fn malformed_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
problem: MalformedPatternProblem,
|
||||
region: Region,
|
||||
) -> Pattern2 {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
|
||||
problem, region,
|
||||
)));
|
||||
|
||||
Pattern2::MalformedPattern(problem, region)
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::{called_via::CalledVia, symbol::Symbol};
|
||||
use roc_parse::ast::StrLiteral;
|
||||
|
||||
use crate::{
|
||||
ast_error::{ASTResult, UnexpectedASTNodeSnafu},
|
||||
lang::{
|
||||
core::expr::{
|
||||
expr2::{ArrString, ARR_STRING_CAPACITY},
|
||||
expr_to_expr2::expr_to_expr2,
|
||||
},
|
||||
env::Env,
|
||||
scope::Scope,
|
||||
},
|
||||
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec},
|
||||
};
|
||||
|
||||
use super::expr::{
|
||||
expr2::{Expr2, ExprId},
|
||||
output::Output,
|
||||
};
|
||||
|
||||
pub(crate) fn flatten_str_literal<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
literal: &StrLiteral<'a>,
|
||||
) -> (Expr2, Output) {
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
|
||||
match literal {
|
||||
PlainLine(str_slice) => {
|
||||
// TODO use smallstr
|
||||
let expr = Expr2::Str(PoolStr::new(str_slice, env.pool));
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Line(segments) => flatten_str_lines(env, scope, &[segments]),
|
||||
Block(lines) => flatten_str_lines(env, scope, lines),
|
||||
}
|
||||
}
|
||||
|
||||
enum StrSegment {
|
||||
Interpolation(Expr2),
|
||||
Plaintext(PoolStr),
|
||||
}
|
||||
|
||||
fn flatten_str_lines<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
lines: &[&[roc_parse::ast::StrSegment<'a>]],
|
||||
) -> (Expr2, Output) {
|
||||
use roc_parse::ast::StrSegment::*;
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut segments = Vec::new();
|
||||
let mut output = Output::default();
|
||||
|
||||
for line in lines {
|
||||
for segment in line.iter() {
|
||||
match segment {
|
||||
Plaintext(string) => {
|
||||
buf.push_str(string);
|
||||
}
|
||||
Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) {
|
||||
Ok(code_pt) => match std::char::from_u32(code_pt) {
|
||||
Some(ch) => {
|
||||
buf.push(ch);
|
||||
}
|
||||
None => {
|
||||
// env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region));
|
||||
//
|
||||
// return (
|
||||
// Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt(
|
||||
// loc_hex_digits.region,
|
||||
// )),
|
||||
// output,
|
||||
// );
|
||||
todo!()
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region));
|
||||
//
|
||||
// return (
|
||||
// Expr::RuntimeError(RuntimeError::InvalidHexadecimal(
|
||||
// loc_hex_digits.region,
|
||||
// )),
|
||||
// output,
|
||||
// );
|
||||
todo!()
|
||||
}
|
||||
},
|
||||
Interpolated(loc_expr) => {
|
||||
if roc_can::expr::is_valid_interpolation(loc_expr.value) {
|
||||
// Interpolations desugar to Str.concat calls
|
||||
output.references.calls.insert(Symbol::STR_CONCAT);
|
||||
|
||||
if !buf.is_empty() {
|
||||
segments.push(StrSegment::Plaintext(PoolStr::new(&buf, env.pool)));
|
||||
|
||||
buf = String::new();
|
||||
}
|
||||
|
||||
let (loc_expr, new_output) =
|
||||
expr_to_expr2(env, scope, loc_expr.value, loc_expr.region);
|
||||
|
||||
output.union(new_output);
|
||||
|
||||
segments.push(StrSegment::Interpolation(loc_expr));
|
||||
} else {
|
||||
// env.problem(Problem::InvalidInterpolation(loc_expr.region));
|
||||
//
|
||||
// return (
|
||||
// Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)),
|
||||
// output,
|
||||
// );
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
EscapedChar(escaped) => buf.push(escaped.unescape()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
segments.push(StrSegment::Plaintext(PoolStr::new(&buf, env.pool)));
|
||||
}
|
||||
|
||||
(desugar_str_segments(env, segments), output)
|
||||
}
|
||||
|
||||
/// Resolve string interpolations by desugaring a sequence of StrSegments
|
||||
/// into nested calls to Str.concat
|
||||
fn desugar_str_segments(env: &mut Env, segments: Vec<StrSegment>) -> Expr2 {
|
||||
use StrSegment::*;
|
||||
|
||||
let pool = &mut env.pool;
|
||||
let var_store = &mut env.var_store;
|
||||
|
||||
let mut iter = segments.into_iter().rev();
|
||||
let mut expr = match iter.next() {
|
||||
Some(Plaintext(pool_str)) => Expr2::Str(pool_str),
|
||||
Some(Interpolation(expr_id)) => expr_id,
|
||||
None => {
|
||||
// No segments? Empty string!
|
||||
|
||||
let pool_str = PoolStr::new("", pool);
|
||||
Expr2::Str(pool_str)
|
||||
}
|
||||
};
|
||||
|
||||
for seg in iter {
|
||||
let new_expr = match seg {
|
||||
Plaintext(string) => Expr2::Str(string),
|
||||
Interpolation(expr_id) => expr_id,
|
||||
};
|
||||
|
||||
let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT));
|
||||
|
||||
let args = vec![
|
||||
(var_store.fresh(), pool.add(new_expr)),
|
||||
(var_store.fresh(), pool.add(expr)),
|
||||
];
|
||||
let args = PoolVec::new(args.into_iter(), pool);
|
||||
|
||||
let new_call = Expr2::Call {
|
||||
args,
|
||||
expr_id: concat_expr_id,
|
||||
expr_var: var_store.fresh(),
|
||||
fn_var: var_store.fresh(),
|
||||
closure_var: var_store.fresh(),
|
||||
called_via: CalledVia::Space,
|
||||
};
|
||||
|
||||
expr = new_call
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
pub fn update_str_expr(
|
||||
node_id: ExprId,
|
||||
new_char: char,
|
||||
insert_index: usize,
|
||||
pool: &mut Pool,
|
||||
) -> ASTResult<()> {
|
||||
let str_expr = pool.get_mut(node_id);
|
||||
|
||||
enum Either {
|
||||
MyArrString(ArrString),
|
||||
OldPoolStr(PoolStr),
|
||||
NewPoolStr(PoolStr),
|
||||
}
|
||||
|
||||
let insert_either = match str_expr {
|
||||
Expr2::SmallStr(arr_string) => {
|
||||
if arr_string.len() < arr_string.capacity() {
|
||||
let mut new_bytes: [u8; ARR_STRING_CAPACITY] = Default::default();
|
||||
let arr_bytes = arr_string.as_str().as_bytes();
|
||||
new_bytes[..insert_index].copy_from_slice(&arr_bytes[..insert_index]);
|
||||
new_bytes[insert_index] = new_char as u8;
|
||||
new_bytes[insert_index + 1..arr_bytes.len() + 1]
|
||||
.copy_from_slice(&arr_bytes[insert_index..]);
|
||||
|
||||
let new_str = unsafe {
|
||||
// all old characters have been checked on file load, new_char has been checked inside editor/src/editor/mvc/ed_update.rs
|
||||
std::str::from_utf8_unchecked(&new_bytes[..arr_bytes.len() + 1])
|
||||
};
|
||||
|
||||
let new_arr_string = match ArrString::from(new_str) {
|
||||
Ok(arr_string) => arr_string,
|
||||
Err(e) => {
|
||||
internal_error!("Failed to build valid ArrayString from str: {:?}", e)
|
||||
}
|
||||
};
|
||||
|
||||
Either::MyArrString(new_arr_string)
|
||||
} else {
|
||||
let mut new_string = arr_string.as_str().to_owned();
|
||||
|
||||
new_string.insert(insert_index, new_char);
|
||||
|
||||
let new_pool_str = PoolStr::new(&new_string, pool);
|
||||
Either::NewPoolStr(new_pool_str)
|
||||
}
|
||||
}
|
||||
Expr2::Str(old_pool_str) => Either::OldPoolStr(*old_pool_str),
|
||||
other => UnexpectedASTNodeSnafu {
|
||||
required_node_type: "SmallStr or Str",
|
||||
encountered_node_type: format!("{other:?}"),
|
||||
}
|
||||
.fail()?,
|
||||
};
|
||||
|
||||
match insert_either {
|
||||
Either::MyArrString(arr_string) => {
|
||||
pool.set(node_id, Expr2::SmallStr(arr_string));
|
||||
}
|
||||
Either::OldPoolStr(old_pool_str) => {
|
||||
let mut new_string = old_pool_str.as_str(pool).to_owned();
|
||||
|
||||
new_string.insert(insert_index, new_char);
|
||||
|
||||
let new_pool_str = PoolStr::new(&new_string, pool);
|
||||
|
||||
pool.set(node_id, Expr2::Str(new_pool_str))
|
||||
}
|
||||
Either::NewPoolStr(new_pool_str) => pool.set(node_id, Expr2::Str(new_pool_str)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,855 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
// use roc_can::expr::Output;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::types::{AliasKind, RecordField};
|
||||
use roc_types::{subs::Variable, types::ErrorType};
|
||||
|
||||
use crate::lang::env::Env;
|
||||
use crate::lang::scope::Scope;
|
||||
use crate::mem_pool::pool::{NodeId, Pool};
|
||||
use crate::mem_pool::pool_str::PoolStr;
|
||||
use crate::mem_pool::pool_vec::PoolVec;
|
||||
use crate::mem_pool::shallow_clone::ShallowClone;
|
||||
|
||||
pub type TypeId = NodeId<Type2>;
|
||||
|
||||
const TYPE2_SIZE: () = assert!(std::mem::size_of::<Type2>() == 3 * 8 + 4);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Type2 {
|
||||
Variable(Variable), // 4B
|
||||
|
||||
Alias(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
|
||||
Opaque(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
|
||||
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
|
||||
|
||||
// 24B
|
||||
HostExposedAlias {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(PoolStr, TypeId)>, // 8B
|
||||
actual_var: Variable, // 4B
|
||||
actual: TypeId, // 4B
|
||||
},
|
||||
EmptyTagUnion,
|
||||
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
|
||||
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
|
||||
|
||||
EmptyRec,
|
||||
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 12B = 8B + 4B
|
||||
|
||||
Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
|
||||
Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
|
||||
|
||||
Erroneous(Problem2), // 24B
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Problem2 {
|
||||
CanonicalizationProblem,
|
||||
CircularType(Symbol, NodeId<ErrorType>), // 12B = 8B + 4B
|
||||
CyclicAlias(Symbol, PoolVec<Symbol>), // 20B = 8B + 12B
|
||||
UnrecognizedIdent(PoolStr), // 8B
|
||||
Shadowed(Loc<PoolStr>),
|
||||
BadTypeArguments {
|
||||
symbol: Symbol, // 8B
|
||||
type_got: u8, // 1B
|
||||
alias_needs: u8, // 1B
|
||||
},
|
||||
InvalidModule,
|
||||
SolvedTypeError,
|
||||
}
|
||||
|
||||
impl ShallowClone for Type2 {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Variable(var) => Self::Variable(*var),
|
||||
Self::Alias(symbol, args, alias_type_id) => {
|
||||
Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone())
|
||||
}
|
||||
Self::Opaque(symbol, args, alias_type_id) => {
|
||||
Self::Opaque(*symbol, args.shallow_clone(), alias_type_id.clone())
|
||||
}
|
||||
Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()),
|
||||
Self::Function(args, closure_type_id, ret_type_id) => Self::Function(
|
||||
args.shallow_clone(),
|
||||
closure_type_id.clone(),
|
||||
ret_type_id.clone(),
|
||||
),
|
||||
rest => todo!("{:?}", rest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type2 {
|
||||
fn substitute(_pool: &mut Pool, _subs: &MutMap<Variable, TypeId>, _type_id: TypeId) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn variables(&self, pool: &mut Pool) -> MutSet<Variable> {
|
||||
use Type2::*;
|
||||
|
||||
let mut stack = vec![self];
|
||||
let mut result = MutSet::default();
|
||||
|
||||
while let Some(this) = stack.pop() {
|
||||
match this {
|
||||
Variable(v) => {
|
||||
result.insert(*v);
|
||||
}
|
||||
Alias(_, _, actual) | AsAlias(_, _, actual) | Opaque(_, _, actual) => {
|
||||
stack.push(pool.get(*actual));
|
||||
}
|
||||
HostExposedAlias {
|
||||
actual_var, actual, ..
|
||||
} => {
|
||||
result.insert(*actual_var);
|
||||
stack.push(pool.get(*actual));
|
||||
}
|
||||
EmptyTagUnion | EmptyRec | Erroneous(_) => {}
|
||||
TagUnion(tags, ext) => {
|
||||
for (_, args) in tags.iter(pool) {
|
||||
stack.extend(args.iter(pool));
|
||||
}
|
||||
stack.push(pool.get(*ext));
|
||||
}
|
||||
RecursiveTagUnion(rec, tags, ext) => {
|
||||
for (_, args) in tags.iter(pool) {
|
||||
stack.extend(args.iter(pool));
|
||||
}
|
||||
stack.push(pool.get(*ext));
|
||||
result.insert(*rec);
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for (_, field) in fields.iter(pool) {
|
||||
stack.push(pool.get(*field.as_inner()));
|
||||
}
|
||||
stack.push(pool.get(*ext));
|
||||
}
|
||||
Function(args, closure, result) => {
|
||||
stack.extend(args.iter(pool));
|
||||
stack.push(pool.get(*closure));
|
||||
stack.push(pool.get(*result));
|
||||
}
|
||||
Apply(_, args) => {
|
||||
stack.extend(args.iter(pool));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn contains_symbol(&self, _pool: &mut Pool, _needle: Symbol) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn substitute_alias(&self, _pool: &mut Pool, _needle: Symbol, _actual: Self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeId<Type2> {
|
||||
pub fn variables(&self, _pool: &mut Pool) -> MutSet<Variable> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// A temporary data structure to return a bunch of values to Def construction
|
||||
pub enum Signature {
|
||||
FunctionWithAliases {
|
||||
annotation: Type2,
|
||||
arguments: PoolVec<Type2>,
|
||||
closure_type_id: TypeId,
|
||||
return_type_id: TypeId,
|
||||
},
|
||||
Function {
|
||||
arguments: PoolVec<Type2>,
|
||||
closure_type_id: TypeId,
|
||||
return_type_id: TypeId,
|
||||
},
|
||||
Value {
|
||||
annotation: Type2,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum Annotation2 {
|
||||
Annotation {
|
||||
named_rigids: MutMap<Lowercase, Variable>,
|
||||
unnamed_rigids: MutSet<Variable>,
|
||||
symbols: MutSet<Symbol>,
|
||||
signature: Signature,
|
||||
},
|
||||
Erroneous,
|
||||
}
|
||||
|
||||
pub fn to_annotation2<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
annotation: &'a roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> Annotation2 {
|
||||
let mut references = References::default();
|
||||
|
||||
let annotation = to_type2(env, scope, &mut references, annotation, region);
|
||||
|
||||
// we dealias until we hit a non-alias, then we either hit a function type (and produce a
|
||||
// function annotation) or anything else (and produce a value annotation)
|
||||
match annotation {
|
||||
Type2::Function(arguments, closure_type_id, return_type_id) => {
|
||||
let References {
|
||||
named,
|
||||
unnamed,
|
||||
symbols,
|
||||
..
|
||||
} = references;
|
||||
|
||||
let signature = Signature::Function {
|
||||
arguments,
|
||||
closure_type_id,
|
||||
return_type_id,
|
||||
};
|
||||
|
||||
Annotation2::Annotation {
|
||||
named_rigids: named,
|
||||
unnamed_rigids: unnamed,
|
||||
symbols,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
Type2::Alias(_, _, _) => {
|
||||
// most of the time, the annotation is not an alias, so this case is comparatively
|
||||
// less efficient
|
||||
shallow_dealias(env, references, annotation)
|
||||
}
|
||||
_ => {
|
||||
let References {
|
||||
named,
|
||||
unnamed,
|
||||
symbols,
|
||||
..
|
||||
} = references;
|
||||
|
||||
let signature = Signature::Value { annotation };
|
||||
|
||||
Annotation2::Annotation {
|
||||
named_rigids: named,
|
||||
unnamed_rigids: unnamed,
|
||||
symbols,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shallow_dealias<'a>(env: &mut Env, references: References, annotation: Type2) -> Annotation2 {
|
||||
let References {
|
||||
named,
|
||||
unnamed,
|
||||
symbols,
|
||||
..
|
||||
} = references;
|
||||
let mut inner = &annotation;
|
||||
|
||||
loop {
|
||||
match inner {
|
||||
Type2::Alias(_, _, actual) => {
|
||||
inner = env.pool.get(*actual);
|
||||
}
|
||||
Type2::Function(arguments, closure_type_id, return_type_id) => {
|
||||
let signature = Signature::FunctionWithAliases {
|
||||
arguments: arguments.shallow_clone(),
|
||||
closure_type_id: *closure_type_id,
|
||||
return_type_id: *return_type_id,
|
||||
annotation,
|
||||
};
|
||||
|
||||
return Annotation2::Annotation {
|
||||
named_rigids: named,
|
||||
unnamed_rigids: unnamed,
|
||||
symbols,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
let signature = Signature::Value { annotation };
|
||||
|
||||
return Annotation2::Annotation {
|
||||
named_rigids: named,
|
||||
unnamed_rigids: unnamed,
|
||||
symbols,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct References {
|
||||
named: MutMap<Lowercase, Variable>,
|
||||
unnamed: MutSet<Variable>,
|
||||
hidden: MutSet<Variable>,
|
||||
symbols: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
pub fn to_type_id<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> TypeId {
|
||||
let type2 = to_type2(env, scope, rigids, annotation, region);
|
||||
|
||||
env.add(type2, region)
|
||||
}
|
||||
|
||||
pub fn as_type_id<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References,
|
||||
type_id: TypeId,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) {
|
||||
let type2 = to_type2(env, scope, rigids, annotation, region);
|
||||
|
||||
env.pool[type_id] = type2;
|
||||
env.set_region(type_id, region);
|
||||
}
|
||||
|
||||
pub fn to_type2<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
references: &mut References,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> Type2 {
|
||||
use roc_parse::ast::Pattern;
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
use roc_parse::ast::TypeHeader;
|
||||
|
||||
match annotation {
|
||||
Apply(module_name, ident, targs) => {
|
||||
match to_type_apply(env, scope, references, module_name, ident, targs, region) {
|
||||
TypeApply::Apply(symbol, args) => {
|
||||
references.symbols.insert(symbol);
|
||||
Type2::Apply(symbol, args)
|
||||
}
|
||||
TypeApply::Alias(symbol, args, actual) => {
|
||||
references.symbols.insert(symbol);
|
||||
Type2::Alias(symbol, args, actual)
|
||||
}
|
||||
TypeApply::Erroneous => {
|
||||
// Type2::Erroneous
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Function(argument_types, return_type) => {
|
||||
let arguments = PoolVec::with_capacity(argument_types.len() as u32, env.pool);
|
||||
|
||||
for (type_id, loc_arg) in arguments.iter_node_ids().zip(argument_types.iter()) {
|
||||
as_type_id(
|
||||
env,
|
||||
scope,
|
||||
references,
|
||||
type_id,
|
||||
&loc_arg.value,
|
||||
loc_arg.region,
|
||||
);
|
||||
}
|
||||
|
||||
let return_type_id = to_type_id(
|
||||
env,
|
||||
scope,
|
||||
references,
|
||||
&return_type.value,
|
||||
return_type.region,
|
||||
);
|
||||
|
||||
let closure_type = Type2::Variable(env.var_store.fresh());
|
||||
let closure_type_id = env.pool.add(closure_type);
|
||||
|
||||
Type2::Function(arguments, closure_type_id, return_type_id)
|
||||
}
|
||||
BoundVariable(v) => {
|
||||
// A rigid type variable. The parser should have already ensured that the name is indeed a lowercase.
|
||||
let v = Lowercase::from(*v);
|
||||
match references.named.get(&v) {
|
||||
Some(var) => Type2::Variable(*var),
|
||||
None => {
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
references.named.insert(v, var);
|
||||
|
||||
Type2::Variable(var)
|
||||
}
|
||||
}
|
||||
}
|
||||
Inferred => {
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
Type2::Variable(var)
|
||||
}
|
||||
Wildcard | Malformed(_) => {
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
references.unnamed.insert(var);
|
||||
|
||||
Type2::Variable(var)
|
||||
}
|
||||
Tuple { elems: _, ext: _ } => {
|
||||
todo!("tuple type");
|
||||
}
|
||||
Record { fields, ext, .. } => {
|
||||
let field_types_map =
|
||||
can_assigned_fields(env, scope, references, &fields.items, region);
|
||||
|
||||
let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool);
|
||||
|
||||
for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) {
|
||||
let poolstr = PoolStr::new(label.as_str(), env.pool);
|
||||
|
||||
let rec_field = field.map_owned(|field| env.pool.add(field));
|
||||
env.pool[node_id] = (poolstr, rec_field);
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region),
|
||||
None => env.add(Type2::EmptyRec, region),
|
||||
};
|
||||
|
||||
Type2::Record(field_types, ext_type)
|
||||
}
|
||||
TagUnion { tags, ext, .. } => {
|
||||
let tag_types_vec = can_tags(env, scope, references, tags.items, region);
|
||||
|
||||
let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool);
|
||||
|
||||
for (node_id, (tag_name, field)) in tag_types.iter_node_ids().zip(tag_types_vec) {
|
||||
env.pool[node_id] = (tag_name, field);
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region),
|
||||
None => env.add(Type2::EmptyTagUnion, region),
|
||||
};
|
||||
|
||||
Type2::TagUnion(tag_types, ext_type)
|
||||
}
|
||||
As(
|
||||
loc_inner,
|
||||
_spaces,
|
||||
TypeHeader {
|
||||
name,
|
||||
vars: loc_vars,
|
||||
},
|
||||
) => {
|
||||
// e.g. `{ x : Int, y : Int } as Point`
|
||||
let symbol = match scope.introduce(
|
||||
name.value.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
) {
|
||||
Ok(symbol) => symbol,
|
||||
|
||||
Err((_original_region, _shadow)) => {
|
||||
// let problem = Problem2::Shadowed(original_region, shadow.clone());
|
||||
|
||||
// env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
|
||||
// original_region,
|
||||
// shadow,
|
||||
// });
|
||||
|
||||
// return Type2::Erroneous(problem);
|
||||
todo!();
|
||||
}
|
||||
};
|
||||
|
||||
let inner_type = to_type2(env, scope, references, &loc_inner.value, region);
|
||||
let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
|
||||
|
||||
let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
|
||||
|
||||
for ((loc_var, named_id), var_id) in loc_vars
|
||||
.iter()
|
||||
.zip(lowercase_vars.iter_node_ids())
|
||||
.zip(vars.iter_node_ids())
|
||||
{
|
||||
let var = match loc_var.value {
|
||||
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
|
||||
name
|
||||
}
|
||||
_ => unreachable!("I thought this was validated during parsing"),
|
||||
};
|
||||
let var_name = Lowercase::from(var);
|
||||
|
||||
if let Some(var) = references.named.get(&var_name) {
|
||||
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
|
||||
|
||||
let type_id = env.pool.add(Type2::Variable(*var));
|
||||
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
|
||||
|
||||
env.pool[named_id] = (poolstr, *var);
|
||||
env.set_region(named_id, loc_var.region);
|
||||
} else {
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
references.named.insert(var_name.clone(), var);
|
||||
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
|
||||
|
||||
let type_id = env.pool.add(Type2::Variable(var));
|
||||
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
|
||||
|
||||
env.pool[named_id] = (poolstr, var);
|
||||
env.set_region(named_id, loc_var.region);
|
||||
}
|
||||
}
|
||||
|
||||
let alias_actual = inner_type;
|
||||
// TODO instantiate recursive tag union
|
||||
// let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type {
|
||||
// let rec_var = env.var_store.fresh();
|
||||
//
|
||||
// let mut new_tags = Vec::with_capacity(tags.len());
|
||||
// for (tag_name, args) in tags {
|
||||
// let mut new_args = Vec::with_capacity(args.len());
|
||||
// for arg in args {
|
||||
// let mut new_arg = arg.clone();
|
||||
// new_arg.substitute_alias(symbol, &Type2::Variable(rec_var));
|
||||
// new_args.push(new_arg);
|
||||
// }
|
||||
// new_tags.push((tag_name.clone(), new_args));
|
||||
// }
|
||||
// Type2::RecursiveTagUnion(rec_var, new_tags, ext)
|
||||
// } else {
|
||||
// inner_type
|
||||
// };
|
||||
|
||||
let mut hidden_variables = MutSet::default();
|
||||
hidden_variables.extend(alias_actual.variables(env.pool));
|
||||
|
||||
for (_, var) in lowercase_vars.iter(env.pool) {
|
||||
hidden_variables.remove(var);
|
||||
}
|
||||
|
||||
let alias_actual_id = env.pool.add(alias_actual);
|
||||
scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id);
|
||||
|
||||
let alias = scope.lookup_alias(symbol).unwrap();
|
||||
// local_aliases.insert(symbol, alias.clone());
|
||||
|
||||
// TODO host-exposed
|
||||
// if vars.is_empty() && env.home == symbol.module_id() {
|
||||
// let actual_var = env.var_store.fresh();
|
||||
// rigids.host_exposed.insert(symbol, actual_var);
|
||||
// Type::HostExposedAlias {
|
||||
// name: symbol,
|
||||
// arguments: vars,
|
||||
// actual: Box::new(alias.typ.clone()),
|
||||
// actual_var,
|
||||
// }
|
||||
// } else {
|
||||
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
|
||||
// }
|
||||
Type2::AsAlias(symbol, vars, alias.actual)
|
||||
}
|
||||
Where { .. } => todo_abilities!(),
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
to_type2(env, scope, references, nested, region)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn can_assigned_fields<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References,
|
||||
fields: &&[Loc<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
|
||||
region: Region,
|
||||
) -> MutMap<Lowercase, RecordField<Type2>> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
use roc_types::types::RecordField::*;
|
||||
|
||||
// SendMap doesn't have a `with_capacity`
|
||||
let mut field_types = MutMap::default();
|
||||
|
||||
// field names we've seen so far in this record
|
||||
let mut seen = std::collections::HashMap::with_capacity(fields.len());
|
||||
|
||||
'outer: for loc_field in fields.iter() {
|
||||
let mut field = &loc_field.value;
|
||||
|
||||
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
|
||||
// when we find the name of this field, break out of the loop
|
||||
// with that value, so we can check whether the field name is
|
||||
// a duplicate
|
||||
let new_name = 'inner: loop {
|
||||
match field {
|
||||
RequiredValue(field_name, _, annotation) => {
|
||||
let field_type =
|
||||
to_type2(env, scope, rigids, &annotation.value, annotation.region);
|
||||
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), Required(field_type));
|
||||
|
||||
break 'inner label;
|
||||
}
|
||||
OptionalValue(field_name, _, annotation) => {
|
||||
let field_type =
|
||||
to_type2(env, scope, rigids, &annotation.value, annotation.region);
|
||||
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), Optional(field_type));
|
||||
|
||||
break 'inner label;
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
let field_type = {
|
||||
if let Some(var) = rigids.named.get(&field_name) {
|
||||
Type2::Variable(*var)
|
||||
} else {
|
||||
let field_var = env.var_store.fresh();
|
||||
rigids.named.insert(field_name.clone(), field_var);
|
||||
Type2::Variable(field_var)
|
||||
}
|
||||
};
|
||||
|
||||
field_types.insert(field_name.clone(), Required(field_type));
|
||||
|
||||
break 'inner field_name;
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
// check the nested field instead
|
||||
field = nested;
|
||||
continue 'inner;
|
||||
}
|
||||
Malformed(_) => {
|
||||
// TODO report this?
|
||||
// completely skip this element, advance to the next tag
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ensure that the new name is not already in this record:
|
||||
// note that the right-most tag wins when there are two with the same name
|
||||
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
|
||||
field_name: new_name.into(),
|
||||
record_region: region,
|
||||
field_region: loc_field.region,
|
||||
replaced_region,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
field_types
|
||||
}
|
||||
|
||||
fn can_tags<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References,
|
||||
tags: &'a [Loc<roc_parse::ast::Tag<'a>>],
|
||||
region: Region,
|
||||
) -> Vec<(TagName, PoolVec<Type2>)> {
|
||||
use roc_parse::ast::Tag;
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
|
||||
// tag names we've seen so far in this tag union
|
||||
let mut seen = std::collections::HashMap::with_capacity(tags.len());
|
||||
|
||||
'outer: for loc_tag in tags.iter() {
|
||||
let mut tag = &loc_tag.value;
|
||||
|
||||
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
|
||||
// when we find the name of this tag, break out of the loop
|
||||
// with that value, so we can check whether the tag name is
|
||||
// a duplicate
|
||||
let new_name = 'inner: loop {
|
||||
match tag {
|
||||
Tag::Apply { name, args } => {
|
||||
let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool);
|
||||
|
||||
for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) {
|
||||
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
|
||||
}
|
||||
|
||||
let tag_name = TagName(name.value.into());
|
||||
tag_types.push((tag_name.clone(), arg_types));
|
||||
|
||||
break 'inner tag_name;
|
||||
}
|
||||
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => {
|
||||
// check the nested tag instead
|
||||
tag = nested;
|
||||
continue 'inner;
|
||||
}
|
||||
Tag::Malformed(_) => {
|
||||
// TODO report this?
|
||||
// completely skip this element, advance to the next tag
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ensure that the new name is not already in this tag union:
|
||||
// note that the right-most tag wins when there are two with the same name
|
||||
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateTag {
|
||||
tag_region: loc_tag.region,
|
||||
tag_union_region: region,
|
||||
replaced_region,
|
||||
tag_name: new_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tag_types
|
||||
}
|
||||
|
||||
enum TypeApply {
|
||||
Apply(Symbol, PoolVec<Type2>),
|
||||
Alias(Symbol, PoolVec<TypeId>, TypeId),
|
||||
Erroneous,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn to_type_apply<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
type_arguments: &[Loc<roc_parse::ast::TypeAnnotation<'a>>],
|
||||
region: Region,
|
||||
) -> TypeApply {
|
||||
let symbol = if module_name.is_empty() {
|
||||
// Since module_name was empty, this is an unqualified type.
|
||||
// Look it up in scope!
|
||||
let ident: Ident = (*ident).into();
|
||||
|
||||
match scope.lookup(&ident, region) {
|
||||
Ok(symbol) => symbol,
|
||||
Err(problem) => {
|
||||
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
||||
|
||||
return TypeApply::Erroneous;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match env.qualified_lookup(module_name, ident, region) {
|
||||
Ok(symbol) => symbol,
|
||||
Err(problem) => {
|
||||
// Either the module wasn't imported, or
|
||||
// it was imported but it doesn't expose this ident.
|
||||
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
||||
|
||||
return TypeApply::Erroneous;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let argument_type_ids = PoolVec::with_capacity(type_arguments.len() as u32, env.pool);
|
||||
|
||||
for (type_id, loc_arg) in argument_type_ids.iter_node_ids().zip(type_arguments.iter()) {
|
||||
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
|
||||
}
|
||||
|
||||
let args = type_arguments;
|
||||
let opt_alias = scope.lookup_alias(symbol);
|
||||
match opt_alias {
|
||||
Some(ref alias) => {
|
||||
// use a known alias
|
||||
let actual = alias.actual;
|
||||
let mut substitutions: MutMap<Variable, TypeId> = MutMap::default();
|
||||
|
||||
if alias.targs.len() != args.len() {
|
||||
return TypeApply::Erroneous;
|
||||
}
|
||||
|
||||
let arguments = PoolVec::with_capacity(type_arguments.len() as u32, env.pool);
|
||||
|
||||
let it = arguments.iter_node_ids().zip(
|
||||
argument_type_ids
|
||||
.iter_node_ids()
|
||||
.zip(alias.targs.iter_node_ids()),
|
||||
);
|
||||
|
||||
for (node_id, (type_id, loc_var_id)) in it {
|
||||
let loc_var = &env.pool[loc_var_id];
|
||||
let name = loc_var.0.shallow_clone();
|
||||
let var = loc_var.1;
|
||||
|
||||
env.pool[node_id] = (name, type_id);
|
||||
|
||||
substitutions.insert(var, type_id);
|
||||
}
|
||||
|
||||
// make sure the recursion variable is freshly instantiated
|
||||
// have to allocate these outside of the if for lifetime reasons...
|
||||
let new = env.var_store.fresh();
|
||||
let fresh = env.pool.add(Type2::Variable(new));
|
||||
if let Type2::RecursiveTagUnion(rvar, ref tags, ext) = &mut env.pool[actual] {
|
||||
substitutions.insert(*rvar, fresh);
|
||||
|
||||
env.pool[actual] = Type2::RecursiveTagUnion(new, tags.shallow_clone(), *ext);
|
||||
}
|
||||
|
||||
// make sure hidden variables are freshly instantiated
|
||||
for var_id in alias.hidden_variables.iter_node_ids() {
|
||||
let var = env.pool[var_id];
|
||||
let fresh = env.pool.add(Type2::Variable(env.var_store.fresh()));
|
||||
substitutions.insert(var, fresh);
|
||||
}
|
||||
|
||||
// instantiate variables
|
||||
Type2::substitute(env.pool, &substitutions, actual);
|
||||
|
||||
let type_arguments = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
||||
|
||||
for (node_id, type_id) in arguments
|
||||
.iter_node_ids()
|
||||
.zip(type_arguments.iter_node_ids())
|
||||
{
|
||||
let typ = env.pool[node_id].1;
|
||||
env.pool[type_id] = typ;
|
||||
}
|
||||
|
||||
TypeApply::Alias(symbol, type_arguments, actual)
|
||||
}
|
||||
None => TypeApply::Apply(symbol, argument_type_ids),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Alias {
|
||||
pub targs: PoolVec<(PoolStr, Variable)>,
|
||||
pub actual: TypeId,
|
||||
|
||||
/// hidden type variables, like the closure variable in `a -> b`
|
||||
pub hidden_variables: PoolVec<Variable>,
|
||||
}
|
||||
|
||||
impl ShallowClone for Alias {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
Self {
|
||||
targs: self.targs.shallow_clone(),
|
||||
hidden_variables: self.hidden_variables.shallow_clone(),
|
||||
actual: self.actual,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
use crate::{
|
||||
lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids},
|
||||
mem_pool::{
|
||||
pool::{NodeId, Pool},
|
||||
shallow_clone::ShallowClone,
|
||||
},
|
||||
};
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use super::{
|
||||
expr::expr2::ExprId,
|
||||
pattern::{Pattern2, PatternId},
|
||||
types::TypeId,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ValueDef {
|
||||
WithAnnotation {
|
||||
pattern_id: PatternId, // 4B
|
||||
expr_id: ExprId, // 4B
|
||||
type_id: TypeId,
|
||||
rigids: Rigids,
|
||||
expr_var: Variable, // 4B
|
||||
},
|
||||
NoAnnotation {
|
||||
pattern_id: PatternId, // 4B
|
||||
expr_id: ExprId, // 4B
|
||||
expr_var: Variable, // 4B
|
||||
},
|
||||
}
|
||||
|
||||
impl ShallowClone for ValueDef {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
match self {
|
||||
Self::WithAnnotation {
|
||||
pattern_id,
|
||||
expr_id,
|
||||
type_id,
|
||||
rigids,
|
||||
expr_var,
|
||||
} => Self::WithAnnotation {
|
||||
pattern_id: *pattern_id,
|
||||
expr_id: *expr_id,
|
||||
type_id: *type_id,
|
||||
rigids: rigids.shallow_clone(),
|
||||
expr_var: *expr_var,
|
||||
},
|
||||
Self::NoAnnotation {
|
||||
pattern_id,
|
||||
expr_id,
|
||||
expr_var,
|
||||
} => Self::NoAnnotation {
|
||||
pattern_id: *pattern_id,
|
||||
expr_id: *expr_id,
|
||||
expr_var: *expr_var,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueDef {
|
||||
pub fn get_expr_id(&self) -> ExprId {
|
||||
match self {
|
||||
ValueDef::WithAnnotation { expr_id, .. } => *expr_id,
|
||||
ValueDef::NoAnnotation { expr_id, .. } => *expr_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pattern_id(&self) -> NodeId<Pattern2> {
|
||||
match self {
|
||||
ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id,
|
||||
ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String {
|
||||
match val_def {
|
||||
ValueDef::WithAnnotation {
|
||||
pattern_id,
|
||||
expr_id,
|
||||
type_id,
|
||||
rigids,
|
||||
expr_var,
|
||||
} => {
|
||||
format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var)
|
||||
}
|
||||
ValueDef::NoAnnotation {
|
||||
pattern_id,
|
||||
expr_id,
|
||||
expr_var,
|
||||
} => {
|
||||
format!(
|
||||
"NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}",
|
||||
pool.get(*pattern_id),
|
||||
expr2_to_string(*expr_id, pool),
|
||||
expr_var
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
use crate::mem_pool::pool::{NodeId, Pool};
|
||||
use bumpalo::{collections::Vec as BumpVec, Bump};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::VarStore;
|
||||
|
||||
use super::core::def::def::References;
|
||||
|
||||
/// TODO document
|
||||
#[derive(Debug)]
|
||||
pub struct Env<'a> {
|
||||
pub home: ModuleId,
|
||||
pub var_store: &'a mut VarStore,
|
||||
pub pool: &'a mut Pool,
|
||||
pub arena: &'a Bump,
|
||||
|
||||
pub problems: BumpVec<'a, Problem>,
|
||||
|
||||
pub dep_idents: IdentIdsByModule,
|
||||
pub module_ids: &'a ModuleIds,
|
||||
pub ident_ids: IdentIds,
|
||||
pub exposed_ident_ids: IdentIds,
|
||||
|
||||
pub closures: MutMap<Symbol, References>,
|
||||
/// Symbols which were referenced by qualified lookups.
|
||||
pub qualified_lookups: MutSet<Symbol>,
|
||||
|
||||
pub top_level_symbols: MutSet<Symbol>,
|
||||
|
||||
pub closure_name_symbol: Option<Symbol>,
|
||||
pub tailcallable_symbol: Option<Symbol>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
home: ModuleId,
|
||||
arena: &'a Bump,
|
||||
pool: &'a mut Pool,
|
||||
var_store: &'a mut VarStore,
|
||||
dep_idents: IdentIdsByModule,
|
||||
module_ids: &'a ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
) -> Env<'a> {
|
||||
Env {
|
||||
home,
|
||||
arena,
|
||||
pool,
|
||||
problems: BumpVec::new_in(arena),
|
||||
var_store,
|
||||
dep_idents,
|
||||
module_ids,
|
||||
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later using Scope.introduce
|
||||
exposed_ident_ids,
|
||||
closures: MutMap::default(),
|
||||
qualified_lookups: MutSet::default(),
|
||||
tailcallable_symbol: None,
|
||||
closure_name_symbol: None,
|
||||
top_level_symbols: MutSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add<T>(&mut self, item: T, region: Region) -> NodeId<T> {
|
||||
let id = self.pool.add(item);
|
||||
self.set_region(id, region);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn problem(&mut self, problem: Problem) {
|
||||
self.problems.push(problem);
|
||||
}
|
||||
|
||||
pub fn set_region<T>(&mut self, _node_id: NodeId<T>, _region: Region) {
|
||||
dbg!("Don't Forget to set the region eventually");
|
||||
}
|
||||
|
||||
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
|
||||
self.closures.insert(symbol, references);
|
||||
}
|
||||
|
||||
/// Generates a unique, new symbol like "$1" or "$5",
|
||||
/// using the home module as the module_id.
|
||||
///
|
||||
/// This is used, for example, during canonicalization of an Expr::Closure
|
||||
/// to generate a unique symbol to refer to that closure.
|
||||
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.ident_ids.gen_unique();
|
||||
|
||||
Symbol::new(self.home, ident_id)
|
||||
}
|
||||
|
||||
/// Returns Err if the symbol resolved, but it was not exposed by the given module
|
||||
pub fn qualified_lookup(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
region: Region,
|
||||
) -> Result<Symbol, RuntimeError> {
|
||||
debug_assert!(
|
||||
!module_name.is_empty(),
|
||||
"Called env.qualified_lookup with an unqualified ident: {ident:?}"
|
||||
);
|
||||
|
||||
let module_name: ModuleName = module_name.into();
|
||||
|
||||
match self.module_ids.get_id(&module_name) {
|
||||
Some(module_id) => {
|
||||
// You can do qualified lookups on your own module, e.g.
|
||||
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
|
||||
if module_id == self.home {
|
||||
match self.ident_ids.get_id(ident) {
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
None => Err(RuntimeError::LookupNotInScope {
|
||||
loc_name: Loc {
|
||||
value: Ident::from(ident),
|
||||
region,
|
||||
},
|
||||
suggestion_options: self
|
||||
.ident_ids
|
||||
.ident_strs()
|
||||
.map(|(_, string)| string.into())
|
||||
.collect(),
|
||||
underscored_suggestion_region: None,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
match self.dep_idents.get(&module_id) {
|
||||
Some(exposed_ids) => match exposed_ids.get_id(ident) {
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
None => {
|
||||
let exposed_values = exposed_ids
|
||||
.ident_strs()
|
||||
.filter(|(_, ident)| {
|
||||
ident.starts_with(|c: char| c.is_lowercase())
|
||||
})
|
||||
.map(|(_, ident)| Lowercase::from(ident))
|
||||
.collect();
|
||||
Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident: Ident::from(ident),
|
||||
region,
|
||||
exposed_values,
|
||||
})
|
||||
}
|
||||
},
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.dep_idents
|
||||
.keys()
|
||||
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||
.map(|module_name| module_name.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: true,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.module_ids
|
||||
.available_modules()
|
||||
.map(|string| string.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod core;
|
||||
pub mod env;
|
||||
mod rigids;
|
||||
pub mod scope;
|
@ -1,83 +0,0 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::BuildHasherDefault,
|
||||
};
|
||||
|
||||
use crate::mem_pool::{
|
||||
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
|
||||
};
|
||||
use roc_collections::all::WyHash;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Rigids {
|
||||
// Rigid type variable = type variable where type is specified by the programmer
|
||||
pub names: PoolVec<(Option<PoolStr>, Variable)>, // 8B
|
||||
padding: [u8; 1],
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
impl Rigids {
|
||||
pub fn new(
|
||||
named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
|
||||
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
|
||||
pool: &mut Pool,
|
||||
) -> Self {
|
||||
let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool);
|
||||
|
||||
let mut temp_names = Vec::new();
|
||||
|
||||
temp_names.extend(named.iter().map(|(name, var)| (Some(name.as_str()), *var)));
|
||||
|
||||
temp_names.extend(unnamed.iter().map(|var| (None, *var)));
|
||||
|
||||
for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) {
|
||||
let poolstr = opt_name.map(|name| PoolStr::new(name, pool));
|
||||
|
||||
pool[node_id] = (poolstr, variable);
|
||||
}
|
||||
|
||||
Self {
|
||||
names,
|
||||
padding: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> {
|
||||
let named = self
|
||||
.names
|
||||
.iter(pool)
|
||||
.filter_map(|(opt_pool_str, var)| {
|
||||
opt_pool_str.as_ref().map(|pool_str| (*pool_str, *var))
|
||||
})
|
||||
.collect::<Vec<(PoolStr, Variable)>>();
|
||||
|
||||
PoolVec::new(named.into_iter(), pool)
|
||||
}
|
||||
|
||||
pub fn unnamed(&self, pool: &mut Pool) -> PoolVec<Variable> {
|
||||
let unnamed = self
|
||||
.names
|
||||
.iter(pool)
|
||||
.filter_map(|(opt_pool_str, var)| {
|
||||
if opt_pool_str.is_none() {
|
||||
Some(*var)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Variable>>();
|
||||
|
||||
PoolVec::new(unnamed.into_iter(), pool)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShallowClone for Rigids {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
Self {
|
||||
names: self.names.shallow_clone(),
|
||||
padding: self.padding,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,351 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::ast_error::ASTResult;
|
||||
use crate::builtin_aliases::{self, BuiltinAlias, FreeVars, SolvedType};
|
||||
use crate::mem_pool::pool::Pool;
|
||||
use crate::mem_pool::pool_str::PoolStr;
|
||||
use crate::mem_pool::pool_vec::PoolVec;
|
||||
use crate::mem_pool::shallow_clone::ShallowClone;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, Lowercase};
|
||||
use roc_module::symbol::{
|
||||
get_module_ident_ids, get_module_ident_ids_mut, IdentIds, IdentIdsByModule, Interns, ModuleId,
|
||||
Symbol,
|
||||
};
|
||||
use roc_problem::can::RuntimeError;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarId, VarStore, Variable};
|
||||
|
||||
use super::core::types::{Alias, Type2, TypeId};
|
||||
use super::env::Env;
|
||||
|
||||
fn solved_type_to_type_id(
|
||||
pool: &mut Pool,
|
||||
solved_type: &SolvedType,
|
||||
free_vars: &mut FreeVars,
|
||||
var_store: &mut VarStore,
|
||||
) -> TypeId {
|
||||
let typ2 = to_type2(pool, solved_type, free_vars, var_store);
|
||||
|
||||
pool.add(typ2)
|
||||
}
|
||||
|
||||
fn to_type2(
|
||||
pool: &mut Pool,
|
||||
solved_type: &SolvedType,
|
||||
free_vars: &mut FreeVars,
|
||||
var_store: &mut VarStore,
|
||||
) -> Type2 {
|
||||
match solved_type {
|
||||
// TODO(opaques): take opaques into account
|
||||
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
|
||||
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
|
||||
|
||||
for (type_variable_node_id, solved_arg) in type_variables
|
||||
.iter_node_ids()
|
||||
.zip(solved_type_variables.iter())
|
||||
{
|
||||
let typ2 = to_type2(pool, solved_arg, free_vars, var_store);
|
||||
|
||||
let node = pool.add(typ2);
|
||||
|
||||
pool[type_variable_node_id] = node;
|
||||
}
|
||||
|
||||
let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store);
|
||||
|
||||
let actual = pool.add(actual_typ2);
|
||||
|
||||
let typ2 = Type2::Alias(*symbol, type_variables, actual);
|
||||
|
||||
typ2
|
||||
}
|
||||
SolvedType::TagUnion(tags, ext) => {
|
||||
let new_tags = PoolVec::with_capacity(tags.len() as u32, pool);
|
||||
|
||||
for (tag_node_id, (tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) {
|
||||
let new_args: PoolVec<Type2> = PoolVec::with_capacity(args.len() as u32, pool);
|
||||
|
||||
for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) {
|
||||
let node = to_type2(pool, arg, free_vars, var_store);
|
||||
|
||||
pool[arg_node_id] = node;
|
||||
}
|
||||
|
||||
pool[tag_node_id] = (tag_name.clone(), new_args);
|
||||
}
|
||||
|
||||
let actual_typ2 = to_type2(pool, ext, free_vars, var_store);
|
||||
|
||||
let actual = pool.add(actual_typ2);
|
||||
|
||||
let typ2 = Type2::TagUnion(new_tags, actual);
|
||||
|
||||
typ2
|
||||
}
|
||||
SolvedType::Flex(var_id) => {
|
||||
Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store))
|
||||
}
|
||||
SolvedType::EmptyTagUnion => Type2::EmptyTagUnion,
|
||||
rest => todo!("{:?}", rest),
|
||||
}
|
||||
}
|
||||
|
||||
fn var_id_to_flex_var(
|
||||
var_id: VarId,
|
||||
free_vars: &mut FreeVars,
|
||||
var_store: &mut VarStore,
|
||||
) -> Variable {
|
||||
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
|
||||
*var
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
free_vars.unnamed_vars.insert(var_id, var);
|
||||
|
||||
var
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scope {
|
||||
/// All the identifiers in scope, mapped to were they were defined and
|
||||
/// the Symbol they resolve to.
|
||||
idents: MutMap<Ident, (Symbol, Region)>,
|
||||
|
||||
/// A cache of all the symbols in scope. This makes lookups much
|
||||
/// faster when checking for unused defs and unused arguments.
|
||||
symbols: MutMap<Symbol, Region>,
|
||||
|
||||
/// The type aliases currently in scope
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
|
||||
/// The current module being processed. This will be used to turn
|
||||
/// unqualified idents into Symbols.
|
||||
home: ModuleId,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope {
|
||||
let solved_aliases = builtin_aliases::aliases();
|
||||
let mut aliases = MutMap::default();
|
||||
|
||||
for (symbol, builtin_alias) in solved_aliases {
|
||||
// let BuiltinAlias { region, vars, typ } = builtin_alias;
|
||||
let BuiltinAlias { vars, typ, .. } = builtin_alias;
|
||||
|
||||
let mut free_vars = FreeVars::default();
|
||||
|
||||
// roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
|
||||
let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store);
|
||||
|
||||
// make sure to sort these variables to make them line up with the type arguments
|
||||
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
|
||||
type_variables.sort();
|
||||
|
||||
debug_assert_eq!(vars.len(), type_variables.len());
|
||||
let variables = PoolVec::with_capacity(vars.len() as u32, pool);
|
||||
|
||||
let it = variables
|
||||
.iter_node_ids()
|
||||
.zip(vars.iter())
|
||||
.zip(type_variables);
|
||||
for ((node_id, loc_name), (_, var)) in it {
|
||||
// TODO region is ignored, but "fake" anyway. How to resolve?
|
||||
let name = PoolStr::new(loc_name.value.as_str(), pool);
|
||||
pool[node_id] = (name, var);
|
||||
}
|
||||
|
||||
let alias = Alias {
|
||||
actual,
|
||||
/// We know that builtin aliases have no hidden variables (e.g. in closures)
|
||||
hidden_variables: PoolVec::empty(pool),
|
||||
targs: variables,
|
||||
};
|
||||
|
||||
aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
let idents = Symbol::apply_types_in_scope();
|
||||
let idents: MutMap<_, _> = idents.into_iter().collect();
|
||||
|
||||
Scope {
|
||||
home,
|
||||
idents,
|
||||
symbols: MutMap::default(),
|
||||
aliases,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idents(&self) -> impl Iterator<Item = (&Ident, &(Symbol, Region))> {
|
||||
self.idents.iter()
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> impl Iterator<Item = (Symbol, Region)> + '_ {
|
||||
self.symbols.iter().map(|(x, y)| (*x, *y))
|
||||
}
|
||||
|
||||
pub fn contains_ident(&self, ident: &Ident) -> bool {
|
||||
self.idents.contains_key(ident)
|
||||
}
|
||||
|
||||
pub fn contains_symbol(&self, symbol: Symbol) -> bool {
|
||||
self.symbols.contains_key(&symbol)
|
||||
}
|
||||
|
||||
pub fn num_idents(&self) -> usize {
|
||||
self.idents.len()
|
||||
}
|
||||
|
||||
pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
|
||||
match self.idents.get(ident) {
|
||||
Some((symbol, _)) => Ok(*symbol),
|
||||
None => Err(RuntimeError::LookupNotInScope {
|
||||
loc_name: Loc {
|
||||
region,
|
||||
value: ident.clone().into(),
|
||||
},
|
||||
suggestion_options: self.idents.keys().map(|v| v.as_ref().into()).collect(),
|
||||
underscored_suggestion_region: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
|
||||
self.aliases.get(&symbol)
|
||||
}
|
||||
|
||||
/// Introduce a new ident to scope.
|
||||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
pub fn introduce(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
) -> Result<Symbol, (Region, Loc<Ident>)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some((_, original_region)) => {
|
||||
let shadow = Loc {
|
||||
value: ident,
|
||||
region,
|
||||
};
|
||||
|
||||
Err((*original_region, shadow))
|
||||
}
|
||||
None => {
|
||||
// If this IdentId was already added previously
|
||||
// when the value was exposed in the module header,
|
||||
// use that existing IdentId. Otherwise, create a fresh one.
|
||||
let ident_id = match exposed_ident_ids.get_id(ident.as_str()) {
|
||||
Some(ident_id) => ident_id,
|
||||
None => all_ident_ids.add_str(ident.as_str()),
|
||||
};
|
||||
|
||||
let symbol = Symbol::new(self.home, ident_id);
|
||||
|
||||
self.symbols.insert(symbol, region);
|
||||
self.idents.insert(ident, (symbol, region));
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignore an identifier.
|
||||
///
|
||||
/// Used for record guards like { x: Just _ }
|
||||
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
|
||||
let ident_id = all_ident_ids.add_str(ident.as_str());
|
||||
Symbol::new(self.home, ident_id)
|
||||
}
|
||||
|
||||
/// Import a Symbol from another module into this module's top-level scope.
|
||||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
pub fn import(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
) -> Result<(), (Symbol, Region)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some(shadowed) => Err(*shadowed),
|
||||
None => {
|
||||
self.symbols.insert(symbol, region);
|
||||
self.idents.insert(ident, (symbol, region));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_alias(
|
||||
&mut self,
|
||||
pool: &mut Pool,
|
||||
name: Symbol,
|
||||
vars: PoolVec<(PoolStr, Variable)>,
|
||||
typ: TypeId,
|
||||
) {
|
||||
let mut hidden_variables = MutSet::default();
|
||||
hidden_variables.extend(typ.variables(pool));
|
||||
|
||||
for loc_var in vars.iter(pool) {
|
||||
hidden_variables.remove(&loc_var.1);
|
||||
}
|
||||
|
||||
let hidden_variables_vec = PoolVec::with_capacity(hidden_variables.len() as u32, pool);
|
||||
|
||||
for (node_id, var) in hidden_variables_vec.iter_node_ids().zip(hidden_variables) {
|
||||
pool[node_id] = var;
|
||||
}
|
||||
|
||||
let alias = Alias {
|
||||
targs: vars,
|
||||
hidden_variables: hidden_variables_vec,
|
||||
actual: typ,
|
||||
};
|
||||
|
||||
self.aliases.insert(name, alias);
|
||||
}
|
||||
|
||||
pub fn contains_alias(&mut self, name: Symbol) -> bool {
|
||||
self.aliases.contains_key(&name)
|
||||
}
|
||||
|
||||
pub fn fill_scope(&mut self, env: &Env, all_ident_ids: &mut IdentIdsByModule) -> ASTResult<()> {
|
||||
let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone();
|
||||
|
||||
for (_, ident_ref) in ident_ids.ident_strs() {
|
||||
self.introduce(
|
||||
ident_ref.into(),
|
||||
&env.exposed_ident_ids,
|
||||
get_module_ident_ids_mut(all_ident_ids, &env.home)?,
|
||||
Region::zero(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ShallowClone for Scope {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
Self {
|
||||
idents: self.idents.clone(),
|
||||
symbols: self.symbols.clone(),
|
||||
aliases: self
|
||||
.aliases
|
||||
.iter()
|
||||
.map(|(s, a)| (*s, a.shallow_clone()))
|
||||
.collect(),
|
||||
home: self.home,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
//! Library for the Roc AST
|
||||
//!
|
||||
//! Code to represent the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)
|
||||
//! as used by the editor. In contrast to the compiler, the types in this AST do
|
||||
//! not keep track of the location of the matching code in the source file.
|
||||
pub mod ast_error;
|
||||
mod builtin_aliases;
|
||||
mod canonicalization;
|
||||
pub mod constrain;
|
||||
pub mod lang;
|
||||
pub mod mem_pool;
|
||||
pub mod module;
|
||||
pub mod parse;
|
||||
pub mod solve_type;
|
@ -1,4 +0,0 @@
|
||||
pub mod pool;
|
||||
pub mod pool_str;
|
||||
pub mod pool_vec;
|
||||
pub mod shallow_clone;
|
@ -1,269 +0,0 @@
|
||||
/// A memory pool of 32-byte nodes. The node value 0 is reserved for the pool's
|
||||
/// use, and valid nodes may never have that value.
|
||||
///
|
||||
/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes
|
||||
/// into one page at a time, and when it runs out, it uses mmap to reserve an
|
||||
/// anonymous memory page in which to store nodes.
|
||||
///
|
||||
/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a
|
||||
/// particular node by its NodeId, which is an opaque wrapper around a pointer.
|
||||
///
|
||||
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
|
||||
/// This is important for performance.
|
||||
use std::any::type_name;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{align_of, size_of, MaybeUninit};
|
||||
|
||||
pub const NODE_BYTES: usize = 32;
|
||||
|
||||
// Each page has 128 slots. Each slot holds one 32B node
|
||||
// This means each page is 4096B, which is the size of a memory page
|
||||
// on typical systems where the compiler will be run.
|
||||
//
|
||||
// Nice things about this system include:
|
||||
// * Allocating a new page is as simple as asking the OS for a memory page.
|
||||
// * Since each node is 32B, each node's memory address will be a multiple of 16.
|
||||
// * Thanks to the free lists and our consistent chunk sizes, we should
|
||||
// end up with very little fragmentation.
|
||||
// * Finding a slot for a given node should be very fast: see if the relevant
|
||||
// free list has any openings; if not, try the next size up.
|
||||
//
|
||||
// Less nice things include:
|
||||
// * This system makes it very hard to ever give a page back to the OS.
|
||||
// We could try doing the Mesh Allocator strategy: whenever we allocate
|
||||
// something, assign it to a random slot in the page, and then periodically
|
||||
// try to merge two pages into one (by locking and remapping them in the OS)
|
||||
// and then returning the redundant physical page back to the OS. This should
|
||||
// work in theory, but is pretty complicated, and we'd need to schedule it.
|
||||
// Keep in mind that we can't use the Mesh Allocator itself because it returns
|
||||
// usize pointers, which would be too big for us to have 16B nodes.
|
||||
// On the plus side, we could be okay with higher memory usage early on,
|
||||
// and then later use the Mesh strategy to reduce long-running memory usage.
|
||||
//
|
||||
// With this system, we can allocate up to 4B nodes. If we wanted to keep
|
||||
// a generational index in there, like https://crates.io/crates/sharded-slab
|
||||
// does, we could use some of the 32 bits for that. For example, if we wanted
|
||||
// to have a 5-bit generational index (supporting up to 32 generations), then
|
||||
// we would have 27 bits remaining, meaning we could only support at most
|
||||
// 134M nodes. Since the editor has a separate Pool for each module, is that
|
||||
// enough for any single module we'll encounter in practice? Probably, and
|
||||
// especially if we allocate super large collection literals on the heap instead
|
||||
// of in the pool.
|
||||
//
|
||||
// Another possible design is to try to catch reuse bugs using an "ASan" like
|
||||
// approach: in development builds, whenever we "free" a particular slot, we
|
||||
// can add it to a dev-build-only "freed nodes" list and don't hand it back
|
||||
// out (so, we leak the memory.) Then we can (again, in development builds only)
|
||||
// check to see if we're about to store something in zeroed-out memory; if so, check
|
||||
// to see if it was
|
||||
|
||||
#[derive(Debug, Eq)]
|
||||
pub struct NodeId<T> {
|
||||
pub(super) index: u32,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for NodeId<T> {
|
||||
fn clone(&self) -> Self {
|
||||
NodeId {
|
||||
index: self.index,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for NodeId<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for NodeId<T> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool {
|
||||
pub(super) nodes: *mut [MaybeUninit<u8>; NODE_BYTES],
|
||||
num_nodes: u32,
|
||||
capacity: u32,
|
||||
// free_1node_slots: Vec<NodeId<T>>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn with_capacity(nodes: u32) -> Self {
|
||||
// round up number of nodes requested to nearest page size in bytes
|
||||
let bytes_per_page = page_size::get();
|
||||
let node_bytes = NODE_BYTES * nodes as usize;
|
||||
let leftover = node_bytes % bytes_per_page;
|
||||
let bytes_to_mmap = if leftover == 0 {
|
||||
node_bytes
|
||||
} else {
|
||||
node_bytes + bytes_per_page - leftover
|
||||
};
|
||||
|
||||
let nodes = unsafe {
|
||||
// mmap anonymous memory pages - that is, contiguous virtual memory
|
||||
// addresses from the OS which will be lazily translated into
|
||||
// physical memory one 4096-byte page at a time, once we actually
|
||||
// try to read or write in that page's address range.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
|
||||
libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
bytes_to_mmap,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::memoryapi::VirtualAlloc;
|
||||
use winapi::um::winnt::PAGE_READWRITE;
|
||||
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE};
|
||||
|
||||
VirtualAlloc(
|
||||
std::ptr::null_mut(),
|
||||
bytes_to_mmap,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE,
|
||||
)
|
||||
}
|
||||
} as *mut [MaybeUninit<u8>; NODE_BYTES];
|
||||
|
||||
// This is our actual capacity, in nodes.
|
||||
// It might be higher than the requested capacity due to rounding up
|
||||
// to nearest page size.
|
||||
let capacity = (bytes_to_mmap / NODE_BYTES) as u32;
|
||||
|
||||
Pool {
|
||||
nodes,
|
||||
num_nodes: 0,
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add<T>(&mut self, node: T) -> NodeId<T> {
|
||||
// It's only safe to store this if T fits in S.
|
||||
debug_assert!(
|
||||
size_of::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}, but it needs to be at most {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>(),
|
||||
NODE_BYTES
|
||||
);
|
||||
|
||||
let node_id = self.reserve(1);
|
||||
|
||||
let node_ptr = self.get_ptr(node_id);
|
||||
|
||||
unsafe { node_ptr.write(MaybeUninit::new(node)) };
|
||||
|
||||
node_id
|
||||
}
|
||||
|
||||
/// Reserves the given number of contiguous node slots, and returns
|
||||
/// the NodeId of the first one. We only allow reserving 2^32 in a row.
|
||||
pub(super) fn reserve<T>(&mut self, nodes: u32) -> NodeId<T> {
|
||||
// TODO once we have a free list, look in there for an open slot first!
|
||||
let index = self.num_nodes;
|
||||
|
||||
if index < self.capacity {
|
||||
self.num_nodes = index + nodes;
|
||||
|
||||
NodeId {
|
||||
index,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
} else {
|
||||
todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'b, T>(&self, node_id: NodeId<T>) -> &'b T {
|
||||
unsafe {
|
||||
let node_ptr = self.get_ptr(node_id) as *const T;
|
||||
|
||||
&*node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
|
||||
unsafe {
|
||||
let node_ptr = self.get_ptr(node_id) as *mut T;
|
||||
|
||||
&mut *node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<T>(&mut self, node_id: NodeId<T>, element: T) {
|
||||
unsafe {
|
||||
let node_ptr = self.get_ptr(node_id);
|
||||
|
||||
node_ptr.write(MaybeUninit::new(element));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ptr<T>(&self, node_id: NodeId<T>) -> *mut MaybeUninit<T> {
|
||||
let node_offset = unsafe { self.nodes.offset(node_id.index as isize) };
|
||||
|
||||
// This checks if the node_offset is aligned to T
|
||||
assert!(0 == (node_offset as usize) & (align_of::<T>() - 1));
|
||||
|
||||
node_offset as *mut MaybeUninit<T>
|
||||
}
|
||||
|
||||
// A node is available iff its bytes are all zeroes
|
||||
#[allow(dead_code)]
|
||||
fn is_available<T>(&self, node_id: NodeId<T>) -> bool {
|
||||
debug_assert_eq!(size_of::<T>(), NODE_BYTES);
|
||||
|
||||
unsafe {
|
||||
let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES];
|
||||
|
||||
*node_ptr == [0; NODE_BYTES]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Index<NodeId<T>> for Pool {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, node_id: NodeId<T>) -> &Self::Output {
|
||||
self.get(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
|
||||
fn index_mut(&mut self, node_id: NodeId<T>) -> &mut Self::Output {
|
||||
self.get_mut(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pool {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
libc::munmap(
|
||||
self.nodes as *mut c_void,
|
||||
NODE_BYTES * self.capacity as usize,
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::memoryapi::VirtualFree;
|
||||
use winapi::um::winnt::MEM_RELEASE;
|
||||
|
||||
VirtualFree(
|
||||
self.nodes as *mut c_void,
|
||||
NODE_BYTES * self.capacity as usize,
|
||||
MEM_RELEASE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
use super::pool::{NodeId, Pool, NODE_BYTES};
|
||||
use super::shallow_clone::ShallowClone;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
/// A string containing at most 2^32 pool-allocated bytes.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PoolStr {
|
||||
first_node_id: NodeId<()>,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_str_size() {
|
||||
assert_eq!(size_of::<PoolStr>(), 8);
|
||||
}
|
||||
|
||||
impl PoolStr {
|
||||
pub fn new(string: &str, pool: &mut Pool) -> Self {
|
||||
debug_assert!(string.len() <= u32::MAX as usize);
|
||||
|
||||
let chars_per_node = NODE_BYTES / size_of::<char>();
|
||||
|
||||
let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32;
|
||||
|
||||
if number_of_nodes > 0 {
|
||||
let first_node_id = pool.reserve(number_of_nodes);
|
||||
let index = first_node_id.index as isize;
|
||||
let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void;
|
||||
|
||||
unsafe {
|
||||
libc::memcpy(
|
||||
next_node_ptr,
|
||||
string.as_ptr() as *const c_void,
|
||||
string.len(),
|
||||
);
|
||||
}
|
||||
|
||||
PoolStr {
|
||||
first_node_id,
|
||||
len: string.len() as u32,
|
||||
}
|
||||
} else {
|
||||
PoolStr {
|
||||
first_node_id: NodeId {
|
||||
index: 0,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self, pool: &Pool) -> &str {
|
||||
unsafe {
|
||||
let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8;
|
||||
|
||||
let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize);
|
||||
|
||||
std::str::from_utf8_unchecked(&node_slice[0..self.len as usize])
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self, pool: &Pool) -> usize {
|
||||
let contents = self.as_str(pool);
|
||||
|
||||
contents.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self, pool: &Pool) -> bool {
|
||||
self.len(pool) == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl ShallowClone for PoolStr {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
// Question: should this fully clone, or is a shallow copy
|
||||
// (and the aliasing it entails) OK?
|
||||
Self {
|
||||
first_node_id: self.first_node_id,
|
||||
len: self.len,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,323 +0,0 @@
|
||||
use super::pool::{NodeId, Pool, NODE_BYTES};
|
||||
use super::shallow_clone::ShallowClone;
|
||||
use std::any::type_name;
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
/// An array of at most 2^32 pool-allocated nodes.
|
||||
#[derive(Debug)]
|
||||
pub struct PoolVec<T> {
|
||||
first_node_id: NodeId<T>,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_vec_size() {
|
||||
assert_eq!(size_of::<PoolVec<()>>(), 8);
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Sized> PoolVec<T> {
|
||||
pub fn empty(pool: &mut Pool) -> Self {
|
||||
Self::new(std::iter::empty(), pool)
|
||||
}
|
||||
|
||||
pub fn with_capacity(len: u32, pool: &mut Pool) -> Self {
|
||||
debug_assert!(
|
||||
size_of::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>()
|
||||
);
|
||||
|
||||
if len == 0 {
|
||||
Self::empty(pool)
|
||||
} else {
|
||||
let first_node_id = pool.reserve(len);
|
||||
|
||||
PoolVec { first_node_id, len }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn new<I: ExactSizeIterator<Item = T>>(nodes: I, pool: &mut Pool) -> Self {
|
||||
debug_assert!(nodes.len() <= u32::MAX as usize);
|
||||
debug_assert!(size_of::<T>() <= NODE_BYTES);
|
||||
|
||||
let len = nodes.len() as u32;
|
||||
|
||||
if len > 0 {
|
||||
let first_node_id = pool.reserve(len);
|
||||
let index = first_node_id.index as isize;
|
||||
let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T;
|
||||
|
||||
for (indx_inc, node) in nodes.enumerate() {
|
||||
unsafe {
|
||||
*next_node_ptr = node;
|
||||
|
||||
next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T;
|
||||
}
|
||||
}
|
||||
|
||||
PoolVec { first_node_id, len }
|
||||
} else {
|
||||
PoolVec {
|
||||
first_node_id: NodeId {
|
||||
index: 0,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator<Item = &'a T> {
|
||||
self.pool_list_iter(pool)
|
||||
}
|
||||
|
||||
pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
|
||||
self.pool_list_iter_mut(pool)
|
||||
}
|
||||
|
||||
pub fn iter_node_ids(&self) -> impl ExactSizeIterator<Item = NodeId<T>> {
|
||||
self.pool_list_iter_node_ids()
|
||||
}
|
||||
|
||||
/// Private version of into_iter which exposes the implementation detail
|
||||
/// of PoolVecIter. We don't want that struct to be public, but we
|
||||
/// actually do want to have this separate function for code reuse
|
||||
/// in the iterator's next() method.
|
||||
#[inline(always)]
|
||||
fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> {
|
||||
PoolVecIter {
|
||||
pool,
|
||||
current_node_id: self.first_node_id,
|
||||
len_remaining: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> {
|
||||
PoolVecIterMut {
|
||||
pool,
|
||||
current_node_id: self.first_node_id,
|
||||
len_remaining: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds<T> {
|
||||
PoolVecIterNodeIds {
|
||||
current_node_id: self.first_node_id,
|
||||
len_remaining: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free<S>(self, pool: &'a mut Pool) {
|
||||
// zero out the memory
|
||||
unsafe {
|
||||
let index = self.first_node_id.index as isize;
|
||||
let node_ptr = pool.nodes.offset(index) as *mut c_void;
|
||||
let bytes = self.len as usize * NODE_BYTES;
|
||||
|
||||
libc::memset(node_ptr, 0, bytes);
|
||||
}
|
||||
|
||||
// TODO insert it into the pool's free list
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ShallowClone for PoolVec<T> {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
// Question: should this fully clone, or is a shallow copy
|
||||
// (and the aliasing it entails) OK?
|
||||
Self {
|
||||
first_node_id: self.first_node_id,
|
||||
len: self.len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PoolVecIter<'a, T> {
|
||||
pool: &'a Pool,
|
||||
current_node_id: NodeId<T>,
|
||||
len_remaining: u32,
|
||||
}
|
||||
|
||||
impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
fn len(&self) -> usize {
|
||||
self.len_remaining as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for PoolVecIter<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let len_remaining = self.len_remaining;
|
||||
|
||||
match len_remaining.cmp(&1) {
|
||||
Ordering::Greater => {
|
||||
// Get the current node
|
||||
let index = self.current_node_id.index;
|
||||
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T;
|
||||
|
||||
// Advance the node pointer to the next node in the current page
|
||||
self.current_node_id = NodeId {
|
||||
index: index + 1,
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
self.len_remaining = len_remaining - 1;
|
||||
|
||||
Some(unsafe { &*node_ptr })
|
||||
}
|
||||
Ordering::Equal => {
|
||||
self.len_remaining = 0;
|
||||
|
||||
// Don't advance the node pointer's node, because that might
|
||||
// advance past the end of the page!
|
||||
|
||||
let index = self.current_node_id.index;
|
||||
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T;
|
||||
|
||||
Some(unsafe { &*node_ptr })
|
||||
}
|
||||
Ordering::Less => {
|
||||
// len_remaining was 0
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PoolVecIterMut<'a, T> {
|
||||
pool: &'a Pool,
|
||||
current_node_id: NodeId<T>,
|
||||
len_remaining: u32,
|
||||
}
|
||||
|
||||
impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
fn len(&self) -> usize {
|
||||
self.len_remaining as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for PoolVecIterMut<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
type Item = &'a mut T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let len_remaining = self.len_remaining;
|
||||
|
||||
match len_remaining.cmp(&1) {
|
||||
Ordering::Greater => {
|
||||
// Get the current node
|
||||
let index = self.current_node_id.index;
|
||||
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T;
|
||||
|
||||
// Advance the node pointer to the next node in the current page
|
||||
self.current_node_id = NodeId {
|
||||
index: index + 1,
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
self.len_remaining = len_remaining - 1;
|
||||
|
||||
Some(unsafe { &mut *node_ptr })
|
||||
}
|
||||
Ordering::Equal => {
|
||||
self.len_remaining = 0;
|
||||
|
||||
// Don't advance the node pointer's node, because that might
|
||||
// advance past the end of the page!
|
||||
|
||||
let index = self.current_node_id.index;
|
||||
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T;
|
||||
|
||||
Some(unsafe { &mut *node_ptr })
|
||||
}
|
||||
Ordering::Less => {
|
||||
// len_remaining was 0
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PoolVecIterNodeIds<T> {
|
||||
current_node_id: NodeId<T>,
|
||||
len_remaining: u32,
|
||||
}
|
||||
|
||||
impl<T> ExactSizeIterator for PoolVecIterNodeIds<T> {
|
||||
fn len(&self) -> usize {
|
||||
self.len_remaining as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Iterator for PoolVecIterNodeIds<T> {
|
||||
type Item = NodeId<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let len_remaining = self.len_remaining;
|
||||
|
||||
match len_remaining.cmp(&1) {
|
||||
Ordering::Greater => {
|
||||
// Get the current node
|
||||
let current = self.current_node_id;
|
||||
let index = current.index;
|
||||
|
||||
// Advance the node pointer to the next node in the current page
|
||||
self.current_node_id = NodeId {
|
||||
index: index + 1,
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
self.len_remaining = len_remaining - 1;
|
||||
|
||||
Some(current)
|
||||
}
|
||||
Ordering::Equal => {
|
||||
self.len_remaining = 0;
|
||||
|
||||
// Don't advance the node pointer's node, because that might
|
||||
// advance past the end of the page!
|
||||
|
||||
Some(self.current_node_id)
|
||||
}
|
||||
Ordering::Less => {
|
||||
// len_remaining was 0
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_vec_iter_test() {
|
||||
let expected_vec: Vec<usize> = vec![2, 4, 8, 16];
|
||||
|
||||
let mut test_pool = Pool::with_capacity(1024);
|
||||
let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool);
|
||||
|
||||
let current_vec: Vec<usize> = pool_vec.iter(&test_pool).copied().collect();
|
||||
|
||||
assert_eq!(current_vec, expected_vec);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
use roc_can::expected::Expected;
|
||||
use roc_can::expected::PExpected;
|
||||
|
||||
/// Clones the outer node, but does not clone any nodeids
|
||||
pub trait ShallowClone {
|
||||
fn shallow_clone(&self) -> Self;
|
||||
}
|
||||
|
||||
impl<T> ShallowClone for Expected<T>
|
||||
where
|
||||
T: ShallowClone,
|
||||
{
|
||||
fn shallow_clone(&self) -> Self {
|
||||
use Expected::*;
|
||||
|
||||
match self {
|
||||
NoExpectation(t) => NoExpectation(t.shallow_clone()),
|
||||
ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region),
|
||||
FromAnnotation(loc_pat, n, source, t) => {
|
||||
FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ShallowClone> ShallowClone for PExpected<T> {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
use PExpected::*;
|
||||
|
||||
match self {
|
||||
NoExpectation(t) => NoExpectation(t.shallow_clone()),
|
||||
ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, Threading};
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
use roc_reporting::report::DEFAULT_PALETTE;
|
||||
use roc_target::TargetInfo;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn load_module(
|
||||
src_file: &Path,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
threading: Threading,
|
||||
) -> LoadedModule {
|
||||
let load_config = LoadConfig {
|
||||
target_info: TargetInfo::default_x86_64(), // editor only needs type info, so this is unused
|
||||
function_kind: roc_solve::FunctionKind::LambdaSet, // TODO the editor may need to dynamically change this
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
palette: DEFAULT_PALETTE,
|
||||
threading,
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
let arena = Bump::new();
|
||||
let loaded =
|
||||
roc_load::load_and_typecheck(&arena, src_file.to_path_buf(), roc_cache_dir, load_config);
|
||||
|
||||
match loaded {
|
||||
Ok(x) => x,
|
||||
Err(roc_load::LoadingProblem::FormattedReport(report)) => {
|
||||
panic!("Failed to load module from src_file: {src_file:?}. Report: {report}");
|
||||
}
|
||||
Err(e) => panic!("Failed to load module from src_file {src_file:?}: {e:?}"),
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod parse_ast;
|
||||
pub mod parse_header;
|
@ -1,54 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_module::symbol::Interns;
|
||||
use roc_region::all::Region;
|
||||
|
||||
use crate::{
|
||||
ast_error::ASTResult,
|
||||
lang::{
|
||||
core::{
|
||||
ast::AST,
|
||||
def::{def2::DefId, def_to_def2::str_to_def2},
|
||||
expr::expr2::Expr2,
|
||||
},
|
||||
env::Env,
|
||||
scope::Scope,
|
||||
},
|
||||
};
|
||||
|
||||
use super::parse_header;
|
||||
|
||||
pub fn parse_from_string<'a>(
|
||||
code_str: &'a str,
|
||||
env: &mut Env<'a>,
|
||||
ast_arena: &'a Bump,
|
||||
interns: &mut Interns,
|
||||
) -> ASTResult<AST> {
|
||||
let blank_line_indx = code_str
|
||||
.find("\n\n")
|
||||
.expect("I was expecting two newline chars to split header and rest of code.");
|
||||
|
||||
let header_str = &code_str[0..blank_line_indx];
|
||||
let tail_str = &code_str[blank_line_indx..];
|
||||
|
||||
let mut scope = Scope::new(env.home, env.pool, env.var_store);
|
||||
scope.fill_scope(env, &mut interns.all_ident_ids)?;
|
||||
|
||||
let region = Region::zero();
|
||||
|
||||
let mut def_ids = Vec::<DefId>::new();
|
||||
|
||||
let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?;
|
||||
|
||||
for def2 in def2_vec {
|
||||
let def_id = env.pool.add(def2);
|
||||
|
||||
def_ids.push(def_id);
|
||||
}
|
||||
|
||||
let ast_node_id = env.pool.add(Expr2::Blank);
|
||||
|
||||
Ok(AST {
|
||||
header: parse_header::parse_from_string(header_str, ast_node_id),
|
||||
def_ids,
|
||||
})
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
use crate::lang::core::{expr::expr2::ExprId, header::AppHeader};
|
||||
|
||||
// TODO don't use mock struct and actually parse string
|
||||
pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader {
|
||||
AppHeader {
|
||||
app_name: "\"untitled-app\"".to_owned(),
|
||||
packages_base: "\"rust-platform/main.roc\"".to_owned(),
|
||||
imports: vec![],
|
||||
provides: vec!["main".to_owned()],
|
||||
ast_node_id,
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -21,8 +21,6 @@ default = ["target-aarch64", "target-x86_64", "target-wasm32"]
|
||||
i386-cli-run = ["target-x86"]
|
||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||
|
||||
editor = ["roc_editor"]
|
||||
|
||||
run-wasm32 = ["roc_wasm_interp"]
|
||||
|
||||
# Compiling for a different target than the current machine can cause linker errors.
|
||||
@ -43,7 +41,6 @@ roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_docs = { path = "../docs" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_gen_llvm = { path = "../compiler/gen_llvm" }
|
||||
|
@ -41,7 +41,6 @@ pub const CMD_BUILD: &str = "build";
|
||||
pub const CMD_RUN: &str = "run";
|
||||
pub const CMD_DEV: &str = "dev";
|
||||
pub const CMD_REPL: &str = "repl";
|
||||
pub const CMD_EDIT: &str = "edit";
|
||||
pub const CMD_DOCS: &str = "docs";
|
||||
pub const CMD_CHECK: &str = "check";
|
||||
pub const CMD_VERSION: &str = "version";
|
||||
@ -142,7 +141,8 @@ pub fn build_app() -> Command {
|
||||
|
||||
let build_target_values_parser =
|
||||
PossibleValuesParser::new(Target::iter().map(Into::<&'static str>::into));
|
||||
let app = Command::new("roc")
|
||||
|
||||
Command::new("roc")
|
||||
.version(concatcp!(VERSION, "\n"))
|
||||
.about("Run the given .roc file, if there are no compilation errors.\nYou can use one of the SUBCOMMANDS below to do something else!")
|
||||
.args_conflicts_with_subcommands(true)
|
||||
@ -332,23 +332,7 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_linker)
|
||||
.arg(flag_prebuilt)
|
||||
.arg(roc_file_to_run)
|
||||
.arg(args_for_app.trailing_var_arg(true));
|
||||
|
||||
if cfg!(feature = "editor") {
|
||||
app.subcommand(
|
||||
Command::new(CMD_EDIT)
|
||||
.about("Launch the Roc editor (Work In Progress)")
|
||||
.arg(
|
||||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.num_args(0..)
|
||||
.required(false)
|
||||
.value_parser(value_parser!(OsString))
|
||||
.help("(optional) The directory or files to open on launch"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
app
|
||||
}
|
||||
.arg(args_for_app.trailing_var_arg(true))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -3,7 +3,7 @@ use roc_build::link::LinkType;
|
||||
use roc_build::program::{check_file, CodeGenBackend};
|
||||
use roc_cli::{
|
||||
build_app, format, test, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_DOCS,
|
||||
CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION,
|
||||
CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION,
|
||||
DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME,
|
||||
GLUE_DIR, GLUE_SPEC, ROC_FILE,
|
||||
};
|
||||
@ -52,9 +52,7 @@ fn main() -> io::Result<()> {
|
||||
LinkType::Executable,
|
||||
)
|
||||
} else {
|
||||
launch_editor(None)?;
|
||||
|
||||
Ok(0)
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
Some((CMD_RUN, matches)) => {
|
||||
@ -213,22 +211,6 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
|
||||
Some((CMD_EDIT, matches)) => {
|
||||
match matches
|
||||
.get_many::<OsString>(DIRECTORY_OR_FILES)
|
||||
.map(|mut values| values.next())
|
||||
{
|
||||
Some(Some(os_string)) => {
|
||||
launch_editor(Some(Path::new(os_string)))?;
|
||||
}
|
||||
_ => {
|
||||
launch_editor(None)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit 0 if the editor exited normally
|
||||
Ok(0)
|
||||
}
|
||||
Some((CMD_DOCS, matches)) => {
|
||||
let root_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
||||
|
||||
@ -333,13 +315,3 @@ fn roc_files_recursive<P: AsRef<Path>>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
|
||||
roc_editor::launch(project_dir_path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "editor"))]
|
||||
fn launch_editor(_project_dir_path: Option<&Path>) -> io::Result<()> {
|
||||
panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!");
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod editor_launch_test {
|
||||
|
||||
use core::time;
|
||||
use std::{
|
||||
env,
|
||||
process::{Command, Stdio},
|
||||
thread,
|
||||
};
|
||||
|
||||
use cli_utils::helpers::build_roc_bin;
|
||||
use roc_cli::CMD_EDIT;
|
||||
use roc_command_utils::root_dir;
|
||||
use std::io::Read;
|
||||
|
||||
#[ignore = "We don't want to bring up the editor window during regular tests, only on specific CI machines."]
|
||||
#[test]
|
||||
fn launch_test() {
|
||||
launch(None);
|
||||
|
||||
// with a file arg
|
||||
launch(Some("roc-projects/new-roc-project-1/main.roc"));
|
||||
|
||||
// with a folder arg
|
||||
launch(Some("roc-projects/new-roc-project-1"));
|
||||
}
|
||||
|
||||
fn launch(arg_path_str_opt: Option<&str>) {
|
||||
let root_dir = root_dir();
|
||||
|
||||
// The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder.
|
||||
env::set_current_dir(&root_dir)
|
||||
.unwrap_or_else(|_| panic!("Failed to set current dir to {root_dir:?}"));
|
||||
|
||||
let roc_binary_path = build_roc_bin(&["--features", "editor"]);
|
||||
|
||||
let mut cmd_args = vec![CMD_EDIT];
|
||||
|
||||
if let Some(arg_path_str) = arg_path_str_opt {
|
||||
cmd_args.push(arg_path_str)
|
||||
}
|
||||
|
||||
let mut roc_process = Command::new(roc_binary_path)
|
||||
.args(cmd_args)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start editor from cli.");
|
||||
|
||||
// wait for editor to show
|
||||
thread::sleep(time::Duration::from_millis(2000));
|
||||
|
||||
// We extract 12 bytes from the logs for verification
|
||||
let mut stdout_buffer = [0; 12];
|
||||
let mut stdout = roc_process.stdout.take().unwrap();
|
||||
stdout.read_exact(&mut stdout_buffer).unwrap();
|
||||
|
||||
match roc_process.try_wait() {
|
||||
Ok(Some(status)) => panic!(
|
||||
"The editor exited with status \"{status}\" but I expected it to still be running."
|
||||
),
|
||||
Ok(None) => {
|
||||
// The editor is still running as desired, we check if logs are as expected:
|
||||
assert_eq!("Loading file", std::str::from_utf8(&stdout_buffer).unwrap());
|
||||
// Kill the editor, we don't want it to stay open forever.
|
||||
roc_process.kill().unwrap();
|
||||
}
|
||||
Err(e) => panic!("Failed to wait launch editor cli command: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "roc_code_markup"
|
||||
description = "Our own markup language for Roc code. Used by the editor and the docs."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_ast = { path = "../ast" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_error_utils = { path = "../utils/error" }
|
||||
|
||||
palette.workspace = true
|
||||
|
||||
bumpalo.workspace = true
|
||||
serde.workspace = true
|
||||
snafu.workspace = true
|
@ -1,22 +0,0 @@
|
||||
use palette::{FromColor, Hsv, LinSrgb, Srgb};
|
||||
|
||||
pub type RgbaTup = (f32, f32, f32, f32);
|
||||
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] {
|
||||
[r, g, b, a]
|
||||
}
|
||||
|
||||
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
|
||||
from_hsba(hue, saturation, brightness, 1.0)
|
||||
}
|
||||
|
||||
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
|
||||
let rgb = LinSrgb::from(Srgb::from_color(Hsv::new(
|
||||
hue as f32,
|
||||
(saturation as f32) / 100.0,
|
||||
(brightness as f32) / 100.0,
|
||||
)));
|
||||
|
||||
(rgb.red, rgb.green, rgb.blue, alpha)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
//! A [markup language](https://en.wikipedia.org/wiki/Markup_language) to display Roc code in the editor.
|
||||
pub mod colors;
|
||||
pub mod markup;
|
||||
pub mod markup_error;
|
||||
pub mod slow_pool;
|
||||
pub mod syntax_highlight;
|
||||
pub mod underline_style;
|
@ -1,124 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use snafu::ensure;
|
||||
|
||||
use crate::markup_error::{CaretNotFoundSnafu, MarkResult};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Caret {
|
||||
pub offset_col: usize,
|
||||
}
|
||||
|
||||
impl Caret {
|
||||
pub fn new_attr(offset_col: usize) -> Attribute {
|
||||
Attribute::Caret {
|
||||
caret: Caret { offset_col },
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SelectionStart {
|
||||
offset_col: usize,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SelectionEnd {
|
||||
offset_col: usize,
|
||||
}
|
||||
|
||||
// Highlight is used for example when searching for a specific string to highlight all search results in the module
|
||||
#[derive(Debug)]
|
||||
pub struct HighlightStart {
|
||||
offset_col: usize,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct HighlightEnd {
|
||||
offset_col: usize,
|
||||
}
|
||||
|
||||
// Underline is used for warnings and errors
|
||||
#[derive(Debug)]
|
||||
pub struct UnderlineStart {
|
||||
offset_col: usize,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct UnderlineEnd {
|
||||
offset_col: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Attribute {
|
||||
// Rust does not yet support types for enum variants so we have to do it like this
|
||||
Caret { caret: Caret },
|
||||
|
||||
SelectionStart { selection_start: SelectionStart },
|
||||
SelectionEnd { selection_end: SelectionEnd },
|
||||
|
||||
HighlightStart { highlight_start: HighlightStart },
|
||||
HighlightEnd { highlight_end: HighlightEnd },
|
||||
|
||||
Underline { underline_spec: UnderlineSpec },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UnderlineSpec {
|
||||
Partial { start: usize, end: usize },
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Attributes {
|
||||
pub all: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
pub fn add(&mut self, attr: Attribute) {
|
||||
self.all.push(attr);
|
||||
}
|
||||
|
||||
pub fn add_caret(&mut self, offset_col: usize) {
|
||||
self.all.push(Attribute::Caret {
|
||||
caret: Caret { offset_col },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> {
|
||||
let mut carets = Vec::new();
|
||||
|
||||
for attr in self.all.iter_mut() {
|
||||
if let Attribute::Caret { caret } = attr {
|
||||
carets.push(caret)
|
||||
}
|
||||
}
|
||||
|
||||
carets
|
||||
}
|
||||
|
||||
pub fn get_carets(&self) -> Vec<Caret> {
|
||||
let mut carets = Vec::new();
|
||||
|
||||
for attr in self.all.iter() {
|
||||
if let Attribute::Caret { caret } = attr {
|
||||
carets.push(*caret)
|
||||
}
|
||||
}
|
||||
|
||||
carets
|
||||
}
|
||||
|
||||
pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> MarkResult<()> {
|
||||
let old_len = self.all.len();
|
||||
|
||||
self.all.retain(|attr| {
|
||||
if let Attribute::Caret { caret } = attr {
|
||||
caret.offset_col != offset_col
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let new_len = self.all.len();
|
||||
|
||||
ensure!(old_len != new_len, CaretNotFoundSnafu { node_id });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
use crate::{
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attribute::Attributes,
|
||||
nodes::MarkupNode,
|
||||
nodes::{self, make_nested_mn},
|
||||
};
|
||||
|
||||
pub fn new_equals_mn() -> MarkupNode {
|
||||
common_text_node(nodes::EQUALS.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_comma_mn() -> MarkupNode {
|
||||
common_text_node(nodes::COMMA.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_dot_mn() -> MarkupNode {
|
||||
common_text_node(nodes::DOT.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_blank_mn() -> MarkupNode {
|
||||
MarkupNode::Blank {
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blank_mn_w_nls(nr_of_newlines: usize) -> MarkupNode {
|
||||
MarkupNode::Blank {
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: nr_of_newlines,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_colon_mn() -> MarkupNode {
|
||||
new_operator_mn(nodes::COLON.to_owned())
|
||||
}
|
||||
|
||||
pub fn new_operator_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_left_accolade_mn() -> MarkupNode {
|
||||
common_text_node(nodes::LEFT_ACCOLADE.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_right_accolade_mn() -> MarkupNode {
|
||||
common_text_node(nodes::RIGHT_ACCOLADE.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_left_square_mn() -> MarkupNode {
|
||||
common_text_node(nodes::LEFT_SQUARE_BR.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_right_square_mn() -> MarkupNode {
|
||||
common_text_node(
|
||||
nodes::RIGHT_SQUARE_BR.to_owned(),
|
||||
HighlightStyle::Bracket,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_func_name_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::FunctionName, 0)
|
||||
}
|
||||
|
||||
pub fn new_arg_name_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::FunctionArgName, 0)
|
||||
}
|
||||
|
||||
pub fn new_arrow_mn(newlines_at_end: usize) -> MarkupNode {
|
||||
common_text_node(
|
||||
nodes::ARROW.to_owned(),
|
||||
HighlightStyle::Operator,
|
||||
newlines_at_end,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_comments_mn(comment: String, newlines_at_end: usize) -> MarkupNode {
|
||||
common_text_node(comment, HighlightStyle::Comment, newlines_at_end)
|
||||
}
|
||||
|
||||
fn common_text_node(
|
||||
content: String,
|
||||
highlight_style: HighlightStyle,
|
||||
newlines_at_end: usize,
|
||||
) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content,
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub const NEW_LINES_AFTER_DEF: usize = 2;
|
||||
|
||||
pub fn new_assign_mn(
|
||||
val_name_mn_id: MarkNodeId,
|
||||
equals_mn_id: MarkNodeId,
|
||||
expr_mark_node_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(
|
||||
vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
|
||||
NEW_LINES_AFTER_DEF,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_module_name_mn_id(mn_ids: Vec<MarkNodeId>, mark_node_pool: &mut SlowPool) -> MarkNodeId {
|
||||
if mn_ids.len() == 1 {
|
||||
*mn_ids.first().unwrap() // safe because we checked the length before
|
||||
} else {
|
||||
let nested_node = make_nested_mn(mn_ids, 0);
|
||||
mark_node_pool.add(nested_node)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_module_var_mn(
|
||||
module_name_id: MarkNodeId,
|
||||
dot_id: MarkNodeId,
|
||||
ident_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(vec![module_name_id, dot_id, ident_id], 0)
|
||||
}
|
||||
|
||||
pub fn if_mn() -> MarkupNode {
|
||||
keyword_mn("if ")
|
||||
}
|
||||
|
||||
pub fn then_mn() -> MarkupNode {
|
||||
keyword_mn(" then ")
|
||||
}
|
||||
|
||||
pub fn else_mn() -> MarkupNode {
|
||||
keyword_mn(" else ")
|
||||
}
|
||||
|
||||
fn keyword_mn(keyword: &str) -> MarkupNode {
|
||||
common_text_node(keyword.to_owned(), HighlightStyle::Keyword, 0)
|
||||
}
|
||||
|
||||
pub fn new_if_expr_mn(
|
||||
if_mn_id: MarkNodeId,
|
||||
cond_expr_mn_id: MarkNodeId,
|
||||
then_mn_id: MarkNodeId,
|
||||
then_expr_mn_id: MarkNodeId,
|
||||
else_mn_id: MarkNodeId,
|
||||
else_expr_mn_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(
|
||||
vec![
|
||||
if_mn_id,
|
||||
cond_expr_mn_id,
|
||||
then_mn_id,
|
||||
then_expr_mn_id,
|
||||
else_mn_id,
|
||||
else_expr_mn_id,
|
||||
],
|
||||
1,
|
||||
)
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
use roc_ast::{
|
||||
ast_error::ASTResult,
|
||||
lang::{core::ast::AST, env::Env},
|
||||
};
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
use crate::{
|
||||
markup::{
|
||||
convert::{from_def2::def2_to_markup, from_header::header_to_markup},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::set_parent_for_all,
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
};
|
||||
|
||||
pub fn ast_to_mark_nodes(
|
||||
env: &mut Env<'_>,
|
||||
ast: &AST,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
interns: &Interns,
|
||||
) -> ASTResult<(Vec<MarkNodeId>, MarkIdAstIdMap)> {
|
||||
let mut mark_id_ast_id_map = MarkIdAstIdMap::default();
|
||||
let mut all_mark_node_ids = vec![header_to_markup(
|
||||
&ast.header,
|
||||
mark_node_pool,
|
||||
&mut mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
for &def_id in ast.def_ids.iter() {
|
||||
// for debugging
|
||||
//println!("{}", def2_to_string(def_id, env.pool));
|
||||
|
||||
let def2 = env.pool.get(def_id);
|
||||
|
||||
let expr2_markup_id = def2_to_markup(
|
||||
env,
|
||||
def2,
|
||||
def_id,
|
||||
mark_node_pool,
|
||||
&mut mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
set_parent_for_all(expr2_markup_id, mark_node_pool);
|
||||
|
||||
all_mark_node_ids.push(expr2_markup_id);
|
||||
}
|
||||
|
||||
Ok((all_mark_node_ids, mark_id_ast_id_map))
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
use crate::{
|
||||
markup::{
|
||||
common_nodes::new_blank_mn_w_nls,
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::MarkupNode,
|
||||
top_level_def::{assignment_mark_node, tld_w_comments_mark_node},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
};
|
||||
|
||||
use super::from_expr2::expr2_to_markup;
|
||||
|
||||
use roc_ast::{
|
||||
ast_error::ASTResult,
|
||||
lang::{
|
||||
core::{
|
||||
ast::ASTNodeId,
|
||||
def::def2::{Def2, DefId},
|
||||
},
|
||||
env::Env,
|
||||
},
|
||||
};
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
pub fn add_node(
|
||||
mark_node: MarkupNode,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node_id = mark_node_pool.add(mark_node);
|
||||
|
||||
mark_id_ast_id_map.insert(mark_node_id, ast_node_id);
|
||||
|
||||
mark_node_id
|
||||
}
|
||||
|
||||
pub fn def2_to_markup(
|
||||
env: &mut Env<'_>,
|
||||
def2: &Def2,
|
||||
def2_node_id: DefId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
interns: &Interns,
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
|
||||
|
||||
let mark_node_id = match def2 {
|
||||
Def2::ValueDef {
|
||||
identifier_id,
|
||||
expr_id,
|
||||
} => {
|
||||
let expr_mn_id = expr2_to_markup(
|
||||
env,
|
||||
env.pool.get(*expr_id),
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
0,
|
||||
)?;
|
||||
|
||||
let tld_mn = assignment_mark_node(
|
||||
*identifier_id,
|
||||
expr_mn_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
env,
|
||||
)?;
|
||||
|
||||
add_node(tld_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Def2::Blank => add_node(
|
||||
new_blank_mn_w_nls(2),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
Def2::CommentsBefore { comments, def_id } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id = def2_to_markup(
|
||||
env,
|
||||
inner_def,
|
||||
*def_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
true,
|
||||
)?;
|
||||
|
||||
add_node(
|
||||
full_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Def2::CommentsAfter { def_id, comments } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id = def2_to_markup(
|
||||
env,
|
||||
inner_def,
|
||||
*def_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
false,
|
||||
)?;
|
||||
|
||||
add_node(
|
||||
full_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(mark_node_id)
|
||||
}
|
@ -1,496 +0,0 @@
|
||||
use crate::{
|
||||
markup::{
|
||||
attribute::Attributes,
|
||||
common_nodes::{
|
||||
new_arg_name_mn, new_arrow_mn, new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn,
|
||||
new_left_accolade_mn, new_left_square_mn, new_operator_mn, new_right_accolade_mn,
|
||||
new_right_square_mn,
|
||||
},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::{
|
||||
get_string, join_mark_nodes_commas, join_mark_nodes_spaces, new_markup_node, MarkupNode,
|
||||
},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use roc_ast::{
|
||||
ast_error::ASTResult,
|
||||
lang::{
|
||||
core::{
|
||||
ast::ASTNodeId,
|
||||
expr::{
|
||||
expr2::{Expr2, ExprId},
|
||||
record_field::RecordField,
|
||||
},
|
||||
pattern::{get_identifier_string, Pattern2},
|
||||
val_def::ValueDef,
|
||||
},
|
||||
env::Env,
|
||||
},
|
||||
};
|
||||
use roc_module::{module_err::ModuleResult, symbol::Interns};
|
||||
|
||||
use super::from_def2::add_node;
|
||||
|
||||
// make Markup Nodes: generate String representation, assign Highlighting Style
|
||||
pub fn expr2_to_markup(
|
||||
env: &Env<'_>,
|
||||
expr2: &Expr2,
|
||||
expr2_node_id: ExprId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
interns: &Interns,
|
||||
indent_level: usize,
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
|
||||
|
||||
// for debugging
|
||||
//println!("EXPR2 {:?}", expr2);
|
||||
|
||||
let mark_node_id = match expr2 {
|
||||
Expr2::SmallInt { text, .. }
|
||||
| Expr2::I128 { text, .. }
|
||||
| Expr2::U128 { text, .. }
|
||||
| Expr2::Float { text, .. } => {
|
||||
let num_str = get_string(env, text);
|
||||
|
||||
new_markup_node(
|
||||
with_indent(indent_level, &num_str),
|
||||
ast_node_id,
|
||||
HighlightStyle::Number,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
||||
Expr2::Str(text) => {
|
||||
let content = format!("\"{}\"", text.as_str(env.pool));
|
||||
|
||||
string_mark_node(
|
||||
&content,
|
||||
indent_level,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Expr2::SmallStr(array_str) => {
|
||||
let content = format!("\"{}\"", array_str.as_str());
|
||||
|
||||
string_mark_node(
|
||||
&content,
|
||||
indent_level,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Expr2::Tag { name, .. } => new_markup_node(
|
||||
with_indent(indent_level, &get_string(env, name)),
|
||||
ast_node_id,
|
||||
HighlightStyle::Type,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
),
|
||||
Expr2::Call { args, expr_id, .. } => {
|
||||
let expr = env.pool.get(*expr_id);
|
||||
let fun_call_mark_id = expr2_to_markup(
|
||||
env,
|
||||
expr,
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?;
|
||||
|
||||
let arg_expr_ids: Vec<ExprId> =
|
||||
args.iter(env.pool).map(|(_, arg_id)| *arg_id).collect();
|
||||
|
||||
let arg_call_mark_ids: Vec<MarkNodeId> = arg_expr_ids
|
||||
.iter()
|
||||
.map(|arg_id| {
|
||||
let arg_expr = env.pool.get(*arg_id);
|
||||
|
||||
expr2_to_markup(
|
||||
env,
|
||||
arg_expr,
|
||||
*arg_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
0,
|
||||
)
|
||||
})
|
||||
.collect::<ASTResult<Vec<MarkNodeId>>>()?;
|
||||
|
||||
let mut args_with_sapces =
|
||||
join_mark_nodes_spaces(arg_call_mark_ids, true, mark_node_pool);
|
||||
|
||||
let mut children_ids = vec![fun_call_mark_id];
|
||||
children_ids.append(&mut args_with_sapces);
|
||||
|
||||
let call_node = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(call_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Var(symbol) => {
|
||||
let text = symbol.fully_qualified(interns, env.home);
|
||||
|
||||
new_markup_node(
|
||||
text.to_string(),
|
||||
ast_node_id,
|
||||
HighlightStyle::Value,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
||||
Expr2::List { elems, .. } => {
|
||||
let mut children_ids = vec![add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
let indexed_node_ids: Vec<(usize, ExprId)> =
|
||||
elems.iter(env.pool).copied().enumerate().collect();
|
||||
|
||||
for (idx, node_id) in indexed_node_ids.iter() {
|
||||
let sub_expr2 = env.pool.get(*node_id);
|
||||
|
||||
children_ids.push(expr2_to_markup(
|
||||
env,
|
||||
sub_expr2,
|
||||
*node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?);
|
||||
|
||||
if idx + 1 < elems.len() {
|
||||
children_ids.push(add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
}
|
||||
}
|
||||
children_ids.push(add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let list_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(list_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::EmptyRecord => {
|
||||
let children_ids = vec![
|
||||
add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
];
|
||||
|
||||
let record_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(record_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Record { fields, .. } => {
|
||||
let mut children_ids = vec![add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
|
||||
let record_field = env.pool.get(field_node_id);
|
||||
|
||||
let field_name = record_field.get_record_field_pool_str();
|
||||
|
||||
children_ids.push(new_markup_node(
|
||||
field_name.as_str(env.pool).to_owned(),
|
||||
ast_node_id,
|
||||
HighlightStyle::RecordField,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
));
|
||||
|
||||
match record_field {
|
||||
RecordField::InvalidLabelOnly(_, _) => (),
|
||||
RecordField::LabelOnly(_, _, _) => (),
|
||||
RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
|
||||
children_ids.push(add_node(
|
||||
new_colon_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
|
||||
children_ids.push(expr2_to_markup(
|
||||
env,
|
||||
sub_expr2,
|
||||
*sub_expr2_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
if idx + 1 < fields.len() {
|
||||
children_ids.push(add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
children_ids.push(add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let record_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(record_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Blank => add_node(
|
||||
new_blank_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
Expr2::LetValue {
|
||||
def_id,
|
||||
body_id: _,
|
||||
body_var: _,
|
||||
} => {
|
||||
let pattern_id = env.pool.get(*def_id).get_pattern_id();
|
||||
|
||||
let pattern2 = env.pool.get(pattern_id);
|
||||
|
||||
let val_name = get_identifier_string(pattern2, interns)?;
|
||||
|
||||
let val_name_mn = MarkupNode::Text {
|
||||
content: val_name,
|
||||
syn_high_style: HighlightStyle::Value,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let val_name_mn_id =
|
||||
add_node(val_name_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let equals_mn_id = add_node(
|
||||
new_equals_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let value_def = env.pool.get(*def_id);
|
||||
|
||||
match value_def {
|
||||
ValueDef::NoAnnotation {
|
||||
pattern_id: _,
|
||||
expr_id,
|
||||
expr_var: _,
|
||||
} => {
|
||||
let body_mn_id = expr2_to_markup(
|
||||
env,
|
||||
env.pool.get(*expr_id),
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?;
|
||||
|
||||
let body_mn = mark_node_pool.get_mut(body_mn_id);
|
||||
body_mn.add_newline_at_end();
|
||||
|
||||
let full_let_mn = MarkupNode::Nested {
|
||||
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
add_node(full_let_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
other => {
|
||||
unimplemented!(
|
||||
"I don't know how to convert {:?} into a MarkupNode yet.",
|
||||
other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr2::Closure {
|
||||
function_type: _,
|
||||
uniq_symbol: _,
|
||||
recursive: _,
|
||||
args,
|
||||
body_id,
|
||||
extra: _,
|
||||
} => {
|
||||
let backslash_mn = new_operator_mn("\\".to_string());
|
||||
let backslash_mn_id = add_node(
|
||||
backslash_mn,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let arg_names: Vec<&str> = args
|
||||
.iter(env.pool)
|
||||
.map(|(_, arg_node_id)| {
|
||||
let arg_pattern2 = env.pool.get(*arg_node_id);
|
||||
|
||||
match arg_pattern2 {
|
||||
Pattern2::Identifier(id_symbol) => {
|
||||
let ident_id = id_symbol.ident_id();
|
||||
|
||||
env.ident_ids.get_name_str_res(ident_id)
|
||||
}
|
||||
Pattern2::Shadowed { shadowed_ident } => {
|
||||
Ok(shadowed_ident.as_str(env.pool))
|
||||
}
|
||||
other => {
|
||||
todo!(
|
||||
"TODO: support the following pattern2 as function arg: {:?}",
|
||||
other
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<ModuleResult<Vec<&str>>>()?;
|
||||
|
||||
let arg_mark_nodes: Vec<_> = arg_names
|
||||
.iter()
|
||||
.map(|arg_name| new_arg_name_mn(arg_name.to_string()))
|
||||
.collect();
|
||||
|
||||
let args_with_commas: Vec<MarkupNode> = join_mark_nodes_commas(arg_mark_nodes);
|
||||
|
||||
let mut args_with_commas_ids: Vec<MarkNodeId> = args_with_commas
|
||||
.into_iter()
|
||||
.map(|mark_node| {
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let arrow_mn = new_arrow_mn(1);
|
||||
let arrow_mn_id = add_node(arrow_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let mut children_ids = vec![backslash_mn_id];
|
||||
children_ids.append(&mut args_with_commas_ids);
|
||||
children_ids.push(arrow_mn_id);
|
||||
|
||||
let args_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
let args_mn_id = add_node(args_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let body_expr = env.pool.get(*body_id);
|
||||
let body_mn_id = expr2_to_markup(
|
||||
env,
|
||||
body_expr,
|
||||
*body_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level + 1,
|
||||
)?;
|
||||
|
||||
let function_mn = MarkupNode::Nested {
|
||||
children_ids: vec![args_mn_id, body_mn_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(function_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::RuntimeError() => new_markup_node(
|
||||
"RunTimeError".to_string(),
|
||||
ast_node_id,
|
||||
HighlightStyle::Blank,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
),
|
||||
rest => todo!("implement expr2_to_markup for {:?}", rest),
|
||||
};
|
||||
|
||||
Ok(mark_node_id)
|
||||
}
|
||||
|
||||
fn with_indent(indent_level: usize, some_str: &str) -> String {
|
||||
let full_indent = std::iter::repeat(" ").take(indent_level * 4);
|
||||
let mut full_string: String = full_indent.collect();
|
||||
|
||||
full_string.push_str(some_str);
|
||||
|
||||
full_string
|
||||
}
|
||||
|
||||
fn string_mark_node(
|
||||
content: &str,
|
||||
indent_level: usize,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
new_markup_node(
|
||||
with_indent(indent_level, content),
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
use roc_ast::lang::core::{ast::ASTNodeId, header::AppHeader};
|
||||
|
||||
use crate::{
|
||||
markup::{
|
||||
attribute::Attributes,
|
||||
common_nodes::{
|
||||
new_comma_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn,
|
||||
new_right_square_mn,
|
||||
},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::{set_parent_for_all, MarkupNode},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use super::from_def2::add_node;
|
||||
|
||||
pub fn header_to_markup(
|
||||
app_header: &AppHeader,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let expr_id = app_header.ast_node_id;
|
||||
let ast_node_id = ASTNodeId::AExprId(expr_id);
|
||||
|
||||
let app_node_id = header_mn(
|
||||
"app ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let app_name_node_id = header_val_mn(
|
||||
app_header.app_name.clone(),
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let full_app_node = MarkupNode::Nested {
|
||||
children_ids: vec![app_node_id, app_name_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let packages_node_id = header_mn(
|
||||
" packages ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_left_acc_node_id = add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_base_node_id = header_val_mn(
|
||||
"pf: ".to_owned(),
|
||||
ast_node_id,
|
||||
HighlightStyle::RecordField,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_val_node_id = header_val_mn(
|
||||
app_header.packages_base.clone(),
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_right_acc_node_id = add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let full_packages_node = MarkupNode::Nested {
|
||||
children_ids: vec![
|
||||
packages_node_id,
|
||||
pack_left_acc_node_id,
|
||||
pack_base_node_id,
|
||||
pack_val_node_id,
|
||||
pack_right_acc_node_id,
|
||||
],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let imports_node_id = header_mn(
|
||||
" imports ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let imports_left_square_node_id = add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut import_child_ids: Vec<MarkNodeId> = add_header_mn_list(
|
||||
&app_header.imports,
|
||||
ast_node_id,
|
||||
HighlightStyle::Import,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let imports_right_square_node_id = add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut full_import_children = vec![imports_node_id, imports_left_square_node_id];
|
||||
|
||||
full_import_children.append(&mut import_child_ids);
|
||||
full_import_children.push(imports_right_square_node_id);
|
||||
|
||||
let full_import_node = MarkupNode::Nested {
|
||||
children_ids: full_import_children,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let provides_node_id = header_mn(
|
||||
" provides ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_left_square_node_id = add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut provides_val_node_ids: Vec<MarkNodeId> = add_header_mn_list(
|
||||
&app_header.provides,
|
||||
ast_node_id,
|
||||
HighlightStyle::Provides,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_right_square_node_id = add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_end_node_id = header_mn(
|
||||
" to pf".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id];
|
||||
|
||||
full_provides_children.append(&mut provides_val_node_ids);
|
||||
full_provides_children.push(provides_right_square_node_id);
|
||||
full_provides_children.push(provides_end_node_id);
|
||||
|
||||
let full_provides_node = MarkupNode::Nested {
|
||||
children_ids: full_provides_children,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let full_app_node_id = add_node(
|
||||
full_app_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_packages_node = add_node(
|
||||
full_packages_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_import_node_id = add_node(
|
||||
full_import_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_provides_node_id = add_node(
|
||||
full_provides_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let header_mark_node = MarkupNode::Nested {
|
||||
children_ids: vec![
|
||||
full_app_node_id,
|
||||
full_packages_node,
|
||||
full_import_node_id,
|
||||
full_provides_node_id,
|
||||
],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let header_mn_id = add_node(
|
||||
header_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
set_parent_for_all(header_mn_id, mark_node_pool);
|
||||
|
||||
header_mn_id
|
||||
}
|
||||
|
||||
// Used for provides and imports
|
||||
fn add_header_mn_list(
|
||||
str_vec: &[String],
|
||||
ast_node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> Vec<MarkNodeId> {
|
||||
let nr_of_elts = str_vec.len();
|
||||
|
||||
str_vec
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(indx, provide_str)| {
|
||||
let provide_str = header_val_mn(
|
||||
provide_str.to_owned(),
|
||||
ast_node_id,
|
||||
highlight_style,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
if indx != nr_of_elts - 1 {
|
||||
vec![
|
||||
provide_str,
|
||||
add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
]
|
||||
} else {
|
||||
vec![provide_str]
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn header_mn(
|
||||
content: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node = MarkupNode::Text {
|
||||
content,
|
||||
syn_high_style: HighlightStyle::PackageRelated,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
|
||||
fn header_val_mn(
|
||||
content: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node = MarkupNode::Text {
|
||||
content,
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod from_ast;
|
||||
pub mod from_def2;
|
||||
pub mod from_expr2;
|
||||
pub mod from_header;
|
@ -1,29 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
|
||||
use crate::markup_error::MarkNodeIdWithoutCorrespondingASTNodeIdSnafu;
|
||||
use crate::{markup_error::MarkResult, slow_pool::MarkNodeId};
|
||||
|
||||
/// A hashmap is wrapped to allow for an easy swap out with more performant alternatives
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MarkIdAstIdMap {
|
||||
map: HashMap<MarkNodeId, ASTNodeId>,
|
||||
}
|
||||
|
||||
impl MarkIdAstIdMap {
|
||||
pub fn insert(&mut self, mn_id: MarkNodeId, ast_id: ASTNodeId) {
|
||||
self.map.insert(mn_id, ast_id);
|
||||
}
|
||||
|
||||
pub fn get(&self, mn_id: MarkNodeId) -> MarkResult<ASTNodeId> {
|
||||
match self.map.get(&mn_id) {
|
||||
Some(ast_node_id) => Ok(*ast_node_id),
|
||||
None => MarkNodeIdWithoutCorrespondingASTNodeIdSnafu {
|
||||
node_id: mn_id,
|
||||
keys_str: format!("{:?}", self.map.keys()),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
pub mod attribute;
|
||||
pub mod common_nodes;
|
||||
pub mod convert;
|
||||
pub mod mark_id_ast_id_map;
|
||||
pub mod nodes;
|
||||
pub mod top_level_def;
|
@ -1,533 +0,0 @@
|
||||
use crate::{
|
||||
markup_error::MarkResult,
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attribute::Attributes, common_nodes::new_comma_mn, convert::from_def2::add_node,
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
};
|
||||
|
||||
use crate::markup_error::{
|
||||
ExpectedTextNodeSnafu, NestedNodeMissingChildSnafu, NestedNodeRequiredSnafu,
|
||||
};
|
||||
use roc_ast::{
|
||||
lang::{core::ast::ASTNodeId, env::Env},
|
||||
mem_pool::pool_str::PoolStr,
|
||||
};
|
||||
use roc_error_utils::{index_of, slice_get};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MarkupNode {
|
||||
Nested {
|
||||
children_ids: Vec<MarkNodeId>,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Text {
|
||||
content: String,
|
||||
syn_high_style: HighlightStyle,
|
||||
attributes: Attributes,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Blank {
|
||||
attributes: Attributes,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Indent {
|
||||
indent_level: usize,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
},
|
||||
}
|
||||
|
||||
impl MarkupNode {
|
||||
pub fn get_parent_id_opt(&self) -> Option<MarkNodeId> {
|
||||
match self {
|
||||
MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt,
|
||||
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt,
|
||||
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt,
|
||||
MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_children_ids(&self) -> Vec<MarkNodeId> {
|
||||
match self {
|
||||
MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(),
|
||||
MarkupNode::Text { .. } => vec![],
|
||||
MarkupNode::Blank { .. } => vec![],
|
||||
MarkupNode::Indent { .. } => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec<MarkNodeId> {
|
||||
if let Some(parent_id) = self.get_parent_id_opt() {
|
||||
let parent_node = mark_node_pool.get(parent_id);
|
||||
|
||||
parent_node.get_children_ids()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
// return (index of child in list of children, closest ast index of child corresponding to ast node)
|
||||
pub fn get_child_indices(
|
||||
&self,
|
||||
mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_id_ast_id_map: &MarkIdAstIdMap,
|
||||
) -> MarkResult<(usize, usize)> {
|
||||
match self {
|
||||
MarkupNode::Nested { children_ids, .. } => {
|
||||
let mut mark_child_index_opt: Option<usize> = None;
|
||||
let mut child_ids_with_ast: Vec<MarkNodeId> = Vec::new();
|
||||
|
||||
for (indx, &mark_child_id) in children_ids.iter().enumerate() {
|
||||
if mark_child_id == mark_node_id {
|
||||
mark_child_index_opt = Some(indx);
|
||||
}
|
||||
|
||||
let child_ast_node_id = mark_id_ast_id_map.get(mark_child_id)?;
|
||||
// a node that points to the same ast_node as the parent is a ',', '[', ']'
|
||||
// those are not "real" ast children
|
||||
if child_ast_node_id != ast_node_id {
|
||||
child_ids_with_ast.push(mark_child_id)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(child_index) = mark_child_index_opt {
|
||||
if child_index == (children_ids.len() - 1) {
|
||||
let ast_child_index = child_ids_with_ast.len();
|
||||
|
||||
Ok((child_index, ast_child_index))
|
||||
} else {
|
||||
// we want to find the index of the closest ast mark node to child_index
|
||||
let mut indices_in_mark = vec![];
|
||||
|
||||
for &c_id in child_ids_with_ast.iter() {
|
||||
indices_in_mark.push(index_of(c_id, children_ids)?);
|
||||
}
|
||||
|
||||
let mut last_diff = usize::MAX;
|
||||
let mut best_index = 0;
|
||||
|
||||
for index in indices_in_mark.iter() {
|
||||
let curr_diff =
|
||||
isize::abs((*index as isize) - (child_index as isize)) as usize;
|
||||
|
||||
if curr_diff >= last_diff {
|
||||
break;
|
||||
} else {
|
||||
last_diff = curr_diff;
|
||||
best_index = *index;
|
||||
}
|
||||
}
|
||||
|
||||
let closest_ast_child = slice_get(best_index, children_ids)?;
|
||||
|
||||
let closest_ast_child_index =
|
||||
index_of(*closest_ast_child, &child_ids_with_ast)?;
|
||||
|
||||
// +1 because we want to insert after ast_child
|
||||
Ok((child_index, closest_ast_child_index + 1))
|
||||
}
|
||||
} else {
|
||||
NestedNodeMissingChildSnafu {
|
||||
node_id: mark_node_id,
|
||||
children_ids: children_ids.clone(),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
_ => NestedNodeRequiredSnafu {
|
||||
node_type: self.node_type_as_string(),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_content(&self) -> String {
|
||||
match self {
|
||||
MarkupNode::Nested { .. } => "".to_owned(),
|
||||
MarkupNode::Text { content, .. } => content.clone(),
|
||||
MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(),
|
||||
MarkupNode::Indent { indent_level, .. } => SINGLE_INDENT.repeat(*indent_level),
|
||||
}
|
||||
}
|
||||
|
||||
// gets content and adds newline from newline_at_end
|
||||
pub fn get_full_content(&self) -> String {
|
||||
let mut full_content = self.get_content();
|
||||
|
||||
for _ in 0..self.get_newlines_at_end() {
|
||||
full_content.push('\n')
|
||||
}
|
||||
|
||||
full_content
|
||||
}
|
||||
|
||||
pub fn get_content_mut(&mut self) -> MarkResult<&mut String> {
|
||||
match self {
|
||||
MarkupNode::Text { content, .. } => Ok(content),
|
||||
_ => ExpectedTextNodeSnafu {
|
||||
function_name: "set_content".to_owned(),
|
||||
node_type: self.node_type_as_string(),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_all_alphanumeric(&self) -> bool {
|
||||
self.get_content()
|
||||
.chars()
|
||||
.all(|chr| chr.is_ascii_alphanumeric())
|
||||
}
|
||||
|
||||
pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> {
|
||||
if let MarkupNode::Nested { children_ids, .. } = self {
|
||||
children_ids.splice(index..index, vec![child_id]);
|
||||
} else {
|
||||
NestedNodeRequiredSnafu {
|
||||
node_type: self.node_type_as_string(),
|
||||
}
|
||||
.fail()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn node_type_as_string(&self) -> String {
|
||||
let type_str = match self {
|
||||
MarkupNode::Nested { .. } => "Nested",
|
||||
MarkupNode::Text { .. } => "Text",
|
||||
MarkupNode::Blank { .. } => "Blank",
|
||||
MarkupNode::Indent { .. } => "Indent",
|
||||
};
|
||||
|
||||
type_str.to_owned()
|
||||
}
|
||||
|
||||
pub fn is_blank(&self) -> bool {
|
||||
matches!(self, MarkupNode::Blank { .. })
|
||||
}
|
||||
|
||||
pub fn is_nested(&self) -> bool {
|
||||
matches!(self, MarkupNode::Nested { .. })
|
||||
}
|
||||
|
||||
pub fn get_newlines_at_end(&self) -> usize {
|
||||
match self {
|
||||
MarkupNode::Nested {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end,
|
||||
MarkupNode::Text {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end,
|
||||
MarkupNode::Blank {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end,
|
||||
MarkupNode::Indent { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_newline_at_end(&mut self) {
|
||||
match self {
|
||||
MarkupNode::Nested {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end += 1,
|
||||
MarkupNode::Text {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end += 1,
|
||||
MarkupNode::Blank {
|
||||
newlines_at_end, ..
|
||||
} => *newlines_at_end += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_nested_mn(children_ids: Vec<MarkNodeId>, newlines_at_end: usize) -> MarkupNode {
|
||||
MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_string(env: &Env<'_>, pool_str: &PoolStr) -> String {
|
||||
pool_str.as_str(env.pool).to_owned()
|
||||
}
|
||||
|
||||
pub const BLANK_PLACEHOLDER: &str = " ";
|
||||
pub const LEFT_ACCOLADE: &str = "{ ";
|
||||
pub const RIGHT_ACCOLADE: &str = " }";
|
||||
pub const LEFT_SQUARE_BR: &str = "[ ";
|
||||
pub const RIGHT_SQUARE_BR: &str = " ]";
|
||||
pub const COLON: &str = ": ";
|
||||
pub const COMMA: &str = ", ";
|
||||
pub const DOT: &str = ".";
|
||||
pub const STRING_QUOTES: &str = "\"\"";
|
||||
pub const EQUALS: &str = " = ";
|
||||
pub const ARROW: &str = " -> ";
|
||||
pub const SINGLE_INDENT: &str = " "; // 4 spaces
|
||||
|
||||
pub fn new_markup_node(
|
||||
text: String,
|
||||
node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
indent_level: usize,
|
||||
) -> MarkNodeId {
|
||||
let content_node = MarkupNode::Text {
|
||||
content: text,
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let content_node_id = add_node(content_node, node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
if indent_level > 0 {
|
||||
let indent_node = MarkupNode::Indent {
|
||||
indent_level,
|
||||
parent_id_opt: None,
|
||||
};
|
||||
|
||||
let indent_node_id = add_node(indent_node, node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let nested_node = MarkupNode::Nested {
|
||||
children_ids: vec![indent_node_id, content_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
add_node(nested_node, node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
} else {
|
||||
content_node_id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) {
|
||||
let node = mark_node_pool.get(markup_node_id);
|
||||
|
||||
if let MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: _,
|
||||
newlines_at_end: _,
|
||||
} = node
|
||||
{
|
||||
// need to clone because of borrowing issues
|
||||
let children_ids_clone = children_ids.clone();
|
||||
|
||||
for child_id in children_ids_clone {
|
||||
set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_parent_for_all_helper(
|
||||
markup_node_id: MarkNodeId,
|
||||
parent_node_id: MarkNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
) {
|
||||
let node = mark_node_pool.get_mut(markup_node_id);
|
||||
|
||||
match node {
|
||||
MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt,
|
||||
..
|
||||
} => {
|
||||
*parent_id_opt = Some(parent_node_id);
|
||||
|
||||
// need to clone because of borrowing issues
|
||||
let children_ids_clone = children_ids.clone();
|
||||
|
||||
for child_id in children_ids_clone {
|
||||
set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool);
|
||||
}
|
||||
}
|
||||
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
|
||||
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
|
||||
MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MarkupNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} ({}, {})",
|
||||
self.node_type_as_string(),
|
||||
self.get_content(),
|
||||
self.get_newlines_at_end()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String {
|
||||
let mut full_string = "\n(mark_node_tree)\n".to_owned();
|
||||
|
||||
let node = mark_node_pool.get(root_node_id);
|
||||
|
||||
writeln!(full_string, "{node} mn_id {root_node_id}\n").unwrap();
|
||||
|
||||
tree_as_string_helper(node, 1, &mut full_string, mark_node_pool);
|
||||
|
||||
full_string
|
||||
}
|
||||
|
||||
fn tree_as_string_helper(
|
||||
node: &MarkupNode,
|
||||
level: usize,
|
||||
tree_string: &mut String,
|
||||
mark_node_pool: &SlowPool,
|
||||
) {
|
||||
for child_id in node.get_children_ids() {
|
||||
let child = mark_node_pool.get(child_id);
|
||||
let child_str = format!("{}", mark_node_pool.get(child_id)).replace('\n', "\\n");
|
||||
|
||||
let mut full_str = std::iter::repeat("|--- ")
|
||||
.take(level)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
.to_owned();
|
||||
|
||||
writeln!(full_str, "{child_str} mn_id {child_id}").unwrap();
|
||||
|
||||
tree_string.push_str(&full_str);
|
||||
|
||||
tree_as_string_helper(child, level + 1, tree_string, mark_node_pool);
|
||||
}
|
||||
}
|
||||
|
||||
// return to the the root parent_id of the current node
|
||||
pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId {
|
||||
let mut curr_mark_node_id = mark_node_id;
|
||||
let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt();
|
||||
|
||||
while let Some(curr_parent_id) = curr_parent_id_opt {
|
||||
curr_mark_node_id = curr_parent_id;
|
||||
curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt();
|
||||
}
|
||||
|
||||
curr_mark_node_id
|
||||
}
|
||||
|
||||
// put space mark nodes between each node in mark_nodes
|
||||
pub fn join_mark_nodes_spaces(
|
||||
mark_nodes_ids: Vec<MarkNodeId>,
|
||||
with_prepend: bool,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
) -> Vec<MarkNodeId> {
|
||||
let space_range_max = if with_prepend {
|
||||
mark_nodes_ids.len()
|
||||
} else {
|
||||
mark_nodes_ids.len() - 1
|
||||
};
|
||||
|
||||
let join_nodes: Vec<MarkNodeId> = (0..space_range_max)
|
||||
.map(|_| {
|
||||
let space_node = MarkupNode::Text {
|
||||
content: " ".to_string(),
|
||||
syn_high_style: HighlightStyle::Blank,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(space_node)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if with_prepend {
|
||||
interleave(join_nodes.into_iter(), mark_nodes_ids)
|
||||
} else {
|
||||
interleave(mark_nodes_ids, join_nodes.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
// put comma mark nodes between each node in mark_nodes
|
||||
pub fn join_mark_nodes_commas(mark_nodes: Vec<MarkupNode>) -> Vec<MarkupNode> {
|
||||
let join_nodes: Vec<MarkupNode> = (0..(mark_nodes.len() - 1))
|
||||
.map(|_| new_comma_mn())
|
||||
.collect();
|
||||
|
||||
interleave(mark_nodes.into_iter(), join_nodes)
|
||||
}
|
||||
|
||||
pub fn mark_nodes_to_string(markup_node_ids: &[MarkNodeId], mark_node_pool: &SlowPool) -> String {
|
||||
let mut all_code_string = String::new();
|
||||
|
||||
for mark_node_id in markup_node_ids.iter() {
|
||||
node_to_string_w_children(*mark_node_id, &mut all_code_string, mark_node_pool)
|
||||
}
|
||||
|
||||
all_code_string
|
||||
}
|
||||
|
||||
pub fn node_to_string_w_children(
|
||||
node_id: MarkNodeId,
|
||||
str_buffer: &mut String,
|
||||
mark_node_pool: &SlowPool,
|
||||
) {
|
||||
let node = mark_node_pool.get(node_id);
|
||||
|
||||
if node.is_nested() {
|
||||
for child_id in node.get_children_ids() {
|
||||
node_to_string_w_children(child_id, str_buffer, mark_node_pool);
|
||||
}
|
||||
for _ in 0..node.get_newlines_at_end() {
|
||||
str_buffer.push('\n')
|
||||
}
|
||||
} else {
|
||||
let node_content_str = node.get_full_content();
|
||||
|
||||
str_buffer.push_str(&node_content_str);
|
||||
}
|
||||
}
|
||||
|
||||
fn interleave<I, J>(i: I, j: J) -> Vec<I::Item>
|
||||
where
|
||||
I: IntoIterator,
|
||||
J: IntoIterator<Item = I::Item>,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
let mut flag = false;
|
||||
|
||||
let mut i = i.into_iter();
|
||||
let mut j = j.into_iter();
|
||||
|
||||
loop {
|
||||
flag = !flag;
|
||||
|
||||
if flag {
|
||||
match i.next() {
|
||||
None => {
|
||||
output.extend(j);
|
||||
break output;
|
||||
}
|
||||
Some(v) => {
|
||||
output.push(v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match j.next() {
|
||||
None => {
|
||||
output.extend(i);
|
||||
break output;
|
||||
}
|
||||
Some(v) => {
|
||||
output.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
use roc_ast::{
|
||||
ast_error::ASTResult,
|
||||
lang::{core::ast::ASTNodeId, env::Env},
|
||||
};
|
||||
use roc_module::symbol::IdentId;
|
||||
|
||||
use crate::{
|
||||
markup::{
|
||||
attribute::Attributes,
|
||||
common_nodes::{new_comments_mn, new_equals_mn},
|
||||
nodes::MarkupNode,
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use super::{
|
||||
common_nodes::new_assign_mn, convert::from_def2::add_node, mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
};
|
||||
|
||||
// represents for example: `main = "Hello, World!"`
|
||||
pub fn assignment_mark_node(
|
||||
identifier_id: IdentId,
|
||||
expr_mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
env: &Env<'_>,
|
||||
) -> ASTResult<MarkupNode> {
|
||||
let val_name = env.ident_ids.get_name_str_res(identifier_id)?;
|
||||
|
||||
let val_name_mn = MarkupNode::Text {
|
||||
content: val_name.to_owned(),
|
||||
syn_high_style: HighlightStyle::Value,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let val_name_mn_id = add_node(val_name_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let equals_mn_id = add_node(
|
||||
new_equals_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
Ok(new_assign_mn(
|
||||
val_name_mn_id,
|
||||
equals_mn_id,
|
||||
expr_mark_node_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn tld_w_comments_mark_node(
|
||||
comments: String,
|
||||
def_mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
comments_before: bool,
|
||||
) -> ASTResult<MarkupNode> {
|
||||
let comment_mn_id = add_node(
|
||||
new_comments_mn(comments, 1),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let children_ids = if comments_before {
|
||||
vec![comment_mn_id, def_mark_node_id]
|
||||
} else {
|
||||
vec![def_mark_node_id, comment_mn_id]
|
||||
};
|
||||
|
||||
let tld_w_comment_node = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 2,
|
||||
};
|
||||
|
||||
Ok(tld_w_comment_node)
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use roc_error_utils::UtilError;
|
||||
use snafu::{Backtrace, NoneError, ResultExt, Snafu};
|
||||
|
||||
use crate::slow_pool::MarkNodeId;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum MarkError {
|
||||
#[snafu(display(
|
||||
"CaretNotFound: No carets were found in the expected node with id {}",
|
||||
node_id
|
||||
))]
|
||||
CaretNotFound {
|
||||
node_id: MarkNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"ExpectedTextNode: the function {} expected a Text node, got {} instead.",
|
||||
function_name,
|
||||
node_type
|
||||
))]
|
||||
ExpectedTextNode {
|
||||
function_name: String,
|
||||
node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"MarkNodeIdWithoutCorrespondingASTNodeId: MarkupNode with id {} was not found in MarkIdAstIdMap, available keys are: {}.",
|
||||
node_id,
|
||||
keys_str
|
||||
))]
|
||||
MarkNodeIdWithoutCorrespondingASTNodeId {
|
||||
node_id: MarkNodeId,
|
||||
keys_str: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
|
||||
NestedNodeMissingChild {
|
||||
node_id: MarkNodeId,
|
||||
children_ids: Vec<MarkNodeId>,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"NestedNodeRequired: required a Nested node at this position, node was a {}.",
|
||||
node_type
|
||||
))]
|
||||
NestedNodeRequired {
|
||||
node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("UtilError: {}", msg))]
|
||||
UtilErrorBacktrace { msg: String, backtrace: Backtrace },
|
||||
}
|
||||
|
||||
pub type MarkResult<T, E = MarkError> = std::result::Result<T, E>;
|
||||
|
||||
impl From<UtilError> for MarkError {
|
||||
fn from(util_err: UtilError) -> Self {
|
||||
let msg = format!("{util_err}");
|
||||
|
||||
// hack to handle MarkError derive
|
||||
let dummy_res: Result<(), NoneError> = Err(NoneError {});
|
||||
dummy_res
|
||||
.context(UtilErrorBacktraceSnafu { msg })
|
||||
.unwrap_err()
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use crate::markup::{mark_id_ast_id_map::MarkIdAstIdMap, nodes::MarkupNode};
|
||||
use std::fmt::Write;
|
||||
|
||||
pub type MarkNodeId = usize;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SlowPool {
|
||||
nodes: Vec<MarkupNode>,
|
||||
}
|
||||
|
||||
impl SlowPool {
|
||||
pub fn add(&mut self, node: MarkupNode) -> MarkNodeId {
|
||||
let id = self.nodes.len();
|
||||
|
||||
self.nodes.push(node);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode {
|
||||
// unwrap because Pool doesn't return Result either
|
||||
self.nodes.get(node_id).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode {
|
||||
// unwrap because Pool doesn't return Result either
|
||||
self.nodes.get_mut(node_id).unwrap()
|
||||
}
|
||||
|
||||
pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) {
|
||||
self.nodes[node_id] = new_node;
|
||||
|
||||
// TODO delete children of old node, this requires SlowPool to be changed to
|
||||
// make sure the indexes still make sense after removal/compaction
|
||||
}
|
||||
|
||||
pub fn debug_string(&self, mark_id_ast_id_map: &MarkIdAstIdMap) -> String {
|
||||
let mut ret_str = String::new();
|
||||
|
||||
for (mark_node_id, node) in self.nodes.iter().enumerate() {
|
||||
let ast_node_id_str = match mark_id_ast_id_map.get(mark_node_id) {
|
||||
Ok(ast_id) => format!("{ast_id:?}"),
|
||||
Err(err) => format!("{err:?}"),
|
||||
};
|
||||
let ast_node_id: String = ast_node_id_str
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_digit())
|
||||
.collect();
|
||||
|
||||
let mut child_str = String::new();
|
||||
|
||||
let node_children = node.get_children_ids();
|
||||
|
||||
if !node_children.is_empty() {
|
||||
child_str = format!("children: {node_children:?}");
|
||||
}
|
||||
|
||||
write!(
|
||||
ret_str,
|
||||
"{}: {} ({}) ast_id {:?} {}",
|
||||
mark_node_id,
|
||||
node.node_type_as_string(),
|
||||
node.get_content(),
|
||||
ast_node_id.parse::<usize>().unwrap(),
|
||||
child_str
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ret_str
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::colors::{from_hsb, RgbaTup};
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum HighlightStyle {
|
||||
Operator, // =+-<>...
|
||||
String,
|
||||
FunctionName,
|
||||
FunctionArgName,
|
||||
Type,
|
||||
Bracket,
|
||||
Number,
|
||||
PackageRelated, // app, packages, imports, exposes, provides...
|
||||
Value,
|
||||
RecordField,
|
||||
Import,
|
||||
Provides,
|
||||
Blank,
|
||||
Comment,
|
||||
DocsComment,
|
||||
UppercaseIdent,
|
||||
LowercaseIdent, // TODO we probably don't want all lowercase identifiers to have the same color?
|
||||
Keyword, // if, else, when...
|
||||
}
|
||||
|
||||
pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
||||
use HighlightStyle::*;
|
||||
|
||||
let almost_white = from_hsb(258, 5, 95);
|
||||
|
||||
let mut highlight_map = HashMap::new();
|
||||
[
|
||||
(Operator, from_hsb(185, 50, 75)),
|
||||
(String, from_hsb(346, 65, 97)),
|
||||
(FunctionName, almost_white),
|
||||
(FunctionArgName, from_hsb(225, 50, 100)),
|
||||
(Type, almost_white),
|
||||
(Bracket, from_hsb(347, 80, 100)),
|
||||
(Number, from_hsb(225, 50, 100)),
|
||||
(PackageRelated, almost_white),
|
||||
(Value, almost_white),
|
||||
(RecordField, from_hsb(258, 50, 90)),
|
||||
(Import, from_hsb(225, 50, 100)),
|
||||
(Provides, from_hsb(225, 50, 100)),
|
||||
(Blank, from_hsb(258, 50, 90)),
|
||||
(Comment, from_hsb(258, 50, 90)), // TODO check color
|
||||
(DocsComment, from_hsb(258, 50, 90)), // TODO check color
|
||||
(UppercaseIdent, almost_white),
|
||||
(LowercaseIdent, from_hsb(225, 50, 100)),
|
||||
(Keyword, almost_white),
|
||||
]
|
||||
.iter()
|
||||
.for_each(|tup| {
|
||||
highlight_map.insert(tup.0, tup.1);
|
||||
});
|
||||
|
||||
highlight_map
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::colors::{from_hsb, RgbaTup};
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum UnderlineStyle {
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
pub fn default_underline_color_map() -> HashMap<UnderlineStyle, RgbaTup> {
|
||||
let mut underline_colors = HashMap::new();
|
||||
|
||||
underline_colors.insert(UnderlineStyle::Error, from_hsb(0, 50, 75));
|
||||
underline_colors.insert(UnderlineStyle::Warning, from_hsb(60, 50, 75));
|
||||
|
||||
underline_colors
|
||||
}
|
@ -8,10 +8,8 @@ license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_ast = { path = "../ast" }
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_code_markup = { path = "../code_markup" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_highlight = { path = "../highlight" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
|
@ -1,58 +0,0 @@
|
||||
[package]
|
||||
name = "roc_editor"
|
||||
description = "An editor for Roc"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
roc_ast = { path = "../ast" }
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_code_markup = { path = "../code_markup" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_command_utils = { path = "../utils/command" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_packaging = { path = "../packaging" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_problem = { path = "../compiler/problem" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_unify = { path = "../compiler/unify" }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
|
||||
arrayvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
bytemuck.workspace = true
|
||||
cgmath.workspace = true
|
||||
colored.workspace = true
|
||||
copypasta.workspace = true
|
||||
fs_extra.workspace = true
|
||||
futures.workspace = true
|
||||
glyph_brush.workspace = true
|
||||
libc.workspace = true
|
||||
log.workspace = true
|
||||
nonempty.workspace = true
|
||||
page_size.workspace = true
|
||||
palette.workspace = true
|
||||
pest.workspace = true
|
||||
pest_derive.workspace = true
|
||||
serde.workspace = true
|
||||
snafu.workspace = true
|
||||
threadpool.workspace = true
|
||||
wgpu.workspace = true
|
||||
wgpu_glyph.workspace = true
|
||||
winit.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
rand.workspace = true
|
||||
tempfile.workspace = true
|
||||
uuid.workspace = true
|
@ -1,78 +0,0 @@
|
||||
# :construction: Work In Progress :construction:
|
||||
|
||||
The editor is a work in progress, only a limited subset of Roc expressions are currently supported.
|
||||
|
||||
Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete, refactoring and never needing to format your code.
|
||||
|
||||
## Getting started
|
||||
|
||||
- Install the compiler, see [here](../../BUILDING_FROM_SOURCE.md).
|
||||
- Run the following from the roc folder:
|
||||
|
||||
```sh
|
||||
cargo run edit
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter problems with integrated graphics hardware, install `mesa-vulkan-drivers` and `vulkan-tools`.
|
||||
|
||||
If you encounter an error like `gfx_backend_vulkan ... Failed to detect any valid GPUs in the current config ...` make sure the correct graphics card drivers are installed. On ubuntu `sudo ubuntu-drivers autoinstall` can resolve the problem.
|
||||
If the error persists, take a look [here](https://www.techpowerup.com/gpu-specs/) to see if your GPU supports vulkan.
|
||||
Use of OpenGL instead of vulkan should be available in several months.
|
||||
|
||||
Make sure to [create an issue](https://github.com/roc-lang/roc/issues/new/choose) if you encounter any problems not listed above.
|
||||
|
||||
## Inspiration
|
||||
|
||||
We thank the following open source projects in particular for inspiring us when designing the Roc editor:
|
||||
|
||||
- [learn-wgpu](https://github.com/sotrh/learn-wgpu)
|
||||
- [rgx](https://github.com/cloudhead/rgx)
|
||||
- [elm-editor](https://github.com/jxxcarlson/elm-editor)
|
||||
- [iced](https://github.com/hecrj/iced)
|
||||
|
||||
## How does it work?
|
||||
|
||||
To take a look behind the scenes, open the editor with `./roc edit` or `cargo run edit` and press F11.
|
||||
This debug view shows important data structures that can be found in `editor/src/editor/mvc/ed_model.rs`.
|
||||
Add or delete some code to see how these data structures are updated.
|
||||
|
||||
From roc to render:
|
||||
|
||||
- `./roc edit` or `cargo run edit` is first handled in `cli/src/main.rs`, from there the editor's launch function is called (`editor/src/editor/main.rs`).
|
||||
- In `run_event_loop` we initialize the winit window, wgpu, the editor's model(`EdModel`) and start the rendering loop.
|
||||
- The `ed_model` is filled in part with data obtained by loading and typechecking the roc file with the same function (`load_and_typecheck`) that is used by the compiler.
|
||||
- `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST).
|
||||
- In the `init_model` function:
|
||||
- The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as
|
||||
three text `MarkupNode`; representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`.
|
||||
- `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk.
|
||||
- `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret.
|
||||
- Back in `editor/src/editor/main.rs` we convert the `EdModel` to `RenderedWgpu` by calling `model_to_wgpu`.
|
||||
- The `RenderedWgpu` is passed to the `glyph_brush` to draw the characters(glyphs) on the screen.
|
||||
|
||||
### Important files
|
||||
|
||||
To understand how the editor works it is useful to know the most important files:
|
||||
|
||||
- editor/src/editor/main.rs
|
||||
- editor/src/editor/mvc/ed_update.rs
|
||||
- editor/src/editor/mvc/ed_model.rs
|
||||
- editor/src/editor/mvc/ed_view.rs
|
||||
- editor/src/editor/render_ast.rs
|
||||
- editor/src/editor/render_debug.rs
|
||||
|
||||
Important folders/files outside the editor folder:
|
||||
|
||||
- code_markup/src/markup/convert
|
||||
- code_markup/src/markup/nodes.rs
|
||||
- ast/src/lang/core/def
|
||||
- ast/src/lang/core/expr
|
||||
- ast/src/lang/core/ast.rs
|
||||
- ast/src/lang/env.rs
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome new contributors :heart: and are happy to help you get started.
|
||||
Check [CONTRIBUTING.md](../../CONTRIBUTING.md) for more info.
|
Binary file not shown.
Before Width: | Height: | Size: 111 KiB |
@ -1,115 +0,0 @@
|
||||
# Snippet ideas
|
||||
|
||||
I think snippet insertion would make for an awesome demo that shows off the potential of the editor and a basic version would not be that difficult to implement.
|
||||
With snippet insertion I mean the following:
|
||||
|
||||
say you have a list of Person records, people in scope
|
||||
you press some keyboard shortcut
|
||||
a text field pops up
|
||||
you enter "sort"
|
||||
we show autocomplete options like sort people by firstName, sort people by lastName and sort people by age. The recommendations can be that good because we know people is the only list in scope and we know which fields are in the Person record.
|
||||
you navigate to sort people by age, press enter and we show in the autocomplete popup: sort people by age descending and sort people by age ascending.
|
||||
you navigate to sort people by age ascending and press Enter
|
||||
The correct Roc code is inserted
|
||||
This is most useful for beginning Roc programmers, but I could see it saving time for experts as well.
|
||||
Novice to expert programmers who are new to Roc can also perfectly describe what they want to happen but may not know the correct syntax, names of builtin functions...
|
||||
Other useful snippet commands for beginning Roc programmers might be empty dict, lambda function, split strVal into chars...
|
||||
Some more advanced snippets: post jsonVal to urlVal, connect to amazon S3, insert function of common algorithm like: sieve of erathostenes, greatest common divider...
|
||||
|
||||
This could remove the need for a lot of googling/stackoverflow, creating a delightful experience that sets us apart from other editors.
|
||||
And contrary to stackoverflow/github copilot, snippets will be written by Roc experts or be easily editable by us. They can also be guaranteed to work for a specific Roc and library version because we update, version, and test them.
|
||||
|
||||
A nice goal to aim for is that the user never needs/wants to leave the editor to look things up.
|
||||
We have way more context inside the editor so we should be able to do better than any general-purpose search engine.
|
||||
|
||||
I think the snippet insertion commands also set us up for quality interaction with users using voice input.
|
||||
|
||||
The CC0 license seems like a good fit for the snippets.
|
||||
|
||||
Fuzzy matching should be done to suggest a closest fuzzy match, so if the user types the snippet command `empty Map`, we should suggest `empty Dict`.
|
||||
|
||||
## Pure Text Snippets
|
||||
|
||||
Pure text snippets are not templates and do not contain typed holes.
|
||||
Fish hooks are used when subvariants should be created e.g.: <collection> means this pure text snippets should be created for all Roc collections such as Dict, Set, List...
|
||||
|
||||
- command: empty <collection>
|
||||
- example: empty dict >> `{::}`
|
||||
- command: <common algorithm>
|
||||
- example: sieve of erathostenes >> `inserts function for sieve of erathostenes`
|
||||
- common algorithms: sieve of erathostenes, greatest common divisor, prime factorisation, A* path finding, Dijkstra's algorithm, Breadth First Search...
|
||||
- command: current date/datetime
|
||||
- example: current datetime >> `now <- Time.now\n`
|
||||
- command: list range 1 to 5
|
||||
- example: [1, 2, 3, 4, 5]
|
||||
- command: use commandline args
|
||||
- command: post/get/put request
|
||||
- command: extract float(s)/number/emal addresses from string. regex match float/number/email address/...
|
||||
- command: execute (bash) command/script
|
||||
- command: cast/convert/parse list of x to list of y
|
||||
- command: pattern match/ match/ switch/ case
|
||||
|
||||
## AST aware snippets
|
||||
|
||||
Snippets are inserted based on type of value on which the cursor is located.
|
||||
|
||||
- command: <all builtins for current type>
|
||||
- example:
|
||||
- We have the cursor like this `people|`
|
||||
- User presses snippet shortcut or dot key
|
||||
- We show a list with all builtin functions for the List type
|
||||
- User chooses contains
|
||||
- We change code to `List.contains people |Blank`
|
||||
- command: Str to chars/charlist
|
||||
|
||||
## Snippets with Typed Holes
|
||||
|
||||
- command: sort ^List *^ (by ^Record Field^) {ascending/descending}
|
||||
- example: sort people by age descending >> ...
|
||||
- command: escape url
|
||||
- example: >> `percEncodedString = Url.percentEncode ^String^`
|
||||
- command: list files in directory
|
||||
- example: >>
|
||||
|
||||
```
|
||||
path <- File.pathFromStr ^String^
|
||||
dirContents <- File.enumerateDir path
|
||||
```
|
||||
|
||||
- command: remove/create file
|
||||
- command: read/write from file
|
||||
- command: concatenate strings
|
||||
- command: trim (newlines) at end/start/right/left
|
||||
- command: evaluate predicate for all in slice/list/array
|
||||
- command: get element at index
|
||||
- command: get char at index
|
||||
- command: reverse stirng
|
||||
- command: lambda/anonymous function
|
||||
- we should auto create type hole commands for all builtins.
|
||||
- example: List has builtins reverse, repeat, len... generated snippet commands should be:
|
||||
- reverse list > List.reverse ^List *^
|
||||
- repeat list > List.repeat ^elem^ ^Nat^
|
||||
- len list (fuzzy matches should be length of list)
|
||||
- append element to list
|
||||
|
||||
## fuzzy matching
|
||||
|
||||
some pairs for fuzzy matching unit tests:
|
||||
|
||||
- hashmap > Dict
|
||||
- map > map (function), Dict
|
||||
- for > map, mapWithIndex, walk, walkBackwards, zip
|
||||
- apply/for yield > map
|
||||
- fold > walk, walkBackwards
|
||||
- foldl > walkBackwards
|
||||
- foldr > walk
|
||||
- head > takeFirst
|
||||
- filter > keepIf
|
||||
|
||||
## Inspiration
|
||||
|
||||
- [grepper](https://www.codegrepper.com/) snippet collection that embeds in google search results. See also this [collection of common questions](https://www.codegrepper.com/code-examples/rust).
|
||||
- [github copilot](https://copilot.github.com/) snippet generation with machine learning
|
||||
- [stackoverflow](https://stackoverflow.com)
|
||||
- [rosetta code](http://www.rosettacode.org/wiki/Rosetta_Code) snippets in many different programming languages. Many [snippets](https://www.rosettacode.org/wiki/Category:Programming_Tasks) are programming contest style problems, but there also problems that demonstrate the use of JSON, SHA-256, read a file line by line...
|
||||
- check docs of popular languages to cross reference function/snippet names for fuzzy matching
|
@ -1,82 +0,0 @@
|
||||
use crate::ui::text::lines::Lines;
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
use crate::ui::ui_error::UIResult;
|
||||
use crate::ui::util::slice_get;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CodeLines {
|
||||
pub lines: Vec<String>,
|
||||
pub nr_of_chars: usize,
|
||||
}
|
||||
|
||||
impl CodeLines {
|
||||
pub fn from_str(code_str: &str) -> CodeLines {
|
||||
CodeLines {
|
||||
lines: code_str.split('\n').map(|s| s.to_owned()).collect(),
|
||||
nr_of_chars: code_str.len(),
|
||||
}
|
||||
}
|
||||
|
||||
// last column of last line
|
||||
pub fn end_txt_pos(&self) -> TextPos {
|
||||
let last_line_nr = self.nr_of_lines() - 1;
|
||||
|
||||
TextPos {
|
||||
line: last_line_nr,
|
||||
column: self.line_len(last_line_nr).unwrap(), // safe because we just calculated last_line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Lines for CodeLines {
|
||||
fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> {
|
||||
let line_string = slice_get(line_nr, &self.lines)?;
|
||||
|
||||
Ok(line_string)
|
||||
}
|
||||
|
||||
fn line_len(&self, line_nr: usize) -> UIResult<usize> {
|
||||
self.get_line_ref(line_nr).map(|line| line.len())
|
||||
}
|
||||
|
||||
fn nr_of_lines(&self) -> usize {
|
||||
self.lines.len()
|
||||
}
|
||||
|
||||
fn nr_of_chars(&self) -> usize {
|
||||
self.nr_of_chars
|
||||
}
|
||||
|
||||
fn all_lines_as_string(&self) -> String {
|
||||
self.lines.join("\n")
|
||||
}
|
||||
|
||||
fn is_last_line(&self, line_nr: usize) -> bool {
|
||||
line_nr == self.nr_of_lines() - 1
|
||||
}
|
||||
|
||||
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
|
||||
Ok(self.get_line_ref(line_nr)?.chars().last())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CodeLines {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for row in &self.lines {
|
||||
let row_str = row
|
||||
.chars()
|
||||
.map(|code_char| format!("{code_char}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
|
||||
let escaped_row_str = row_str.replace('\n', "\\n");
|
||||
|
||||
write!(f, "\n{escaped_row_str}")?;
|
||||
}
|
||||
|
||||
writeln!(f, " (code_lines, {:?} lines)", self.lines.len())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::editor::theme::EdTheme;
|
||||
|
||||
use super::resources::strings::START_TIP;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub code_font_size: f32,
|
||||
pub debug_font_size: f32,
|
||||
pub ed_theme: EdTheme,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
code_font_size: 30.0,
|
||||
debug_font_size: 20.0,
|
||||
ed_theme: EdTheme::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn make_code_txt_xy(&self) -> (f32, f32) {
|
||||
(
|
||||
self.code_font_size,
|
||||
(START_TIP.matches('\n').count() as f32 + 2.0) * self.code_font_size,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,365 +0,0 @@
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
use colored::*;
|
||||
use roc_ast::ast_error::ASTError;
|
||||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
use roc_code_markup::markup_error::MarkError;
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
use roc_module::module_err::ModuleError;
|
||||
use snafu::{Backtrace, ErrorCompat, Snafu};
|
||||
|
||||
//import errors as follows:
|
||||
// `use crate::error::OutOfBounds;`
|
||||
// *not* `use crate::error::EdError::OutOfBounds;`
|
||||
// see https://github.com/shepmaster/snafu/issues/211
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum EdError {
|
||||
#[snafu(display(
|
||||
"ASTNodeIdWithoutDefId: The expr_id_opt in ASTNode({:?}) was `None` but I was expecting `Some(DefId)` .",
|
||||
ast_node_id
|
||||
))]
|
||||
ASTNodeIdWithoutDefId {
|
||||
ast_node_id: ASTNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expecting `Some(ExprId)` .",
|
||||
ast_node_id
|
||||
))]
|
||||
ASTNodeIdWithoutExprId {
|
||||
ast_node_id: ASTNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"CaretNotFound: No carets were found in the expected node with id {}",
|
||||
node_id
|
||||
))]
|
||||
CaretNotFound {
|
||||
node_id: MarkNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("ClipboardReadFailed: could not get clipboard contents: {}", err_msg))]
|
||||
ClipboardReadFailed {
|
||||
err_msg: String,
|
||||
},
|
||||
|
||||
#[snafu(display("ClipboardWriteFailed: could not set clipboard contents: {}", err_msg))]
|
||||
ClipboardWriteFailed {
|
||||
err_msg: String,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"ClipboardInitFailed: could not initialize ClipboardContext: {}.",
|
||||
err_msg
|
||||
))]
|
||||
ClipboardInitFailed {
|
||||
err_msg: String,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"ExpectedTextNode: the function {} expected a Text node, got {} instead.",
|
||||
function_name,
|
||||
node_type
|
||||
))]
|
||||
ExpectedTextNode {
|
||||
function_name: String,
|
||||
node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface, package, or platform header. The code string was empty.",
|
||||
))]
|
||||
EmptyCodeString {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("FailedToUpdateIdentIdName: {}", err_str))]
|
||||
FailedToUpdateIdentIdName {
|
||||
err_str: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
|
||||
GetContentOnNestedNode {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"IndexOfFailed: Element {} was not found in collection {}.",
|
||||
elt_str,
|
||||
collection_str
|
||||
))]
|
||||
IndexOfFailed {
|
||||
elt_str: String,
|
||||
collection_str: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
|
||||
KeyNotFound {
|
||||
key_str: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"MissingParent: MarkupNode with id {} should have a parent but there was none.",
|
||||
node_id
|
||||
))]
|
||||
MissingParent {
|
||||
node_id: MarkNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"MissingSelection: ed_model.selected_expr2_id was Some(ExprId) but ed_model.caret_w_sel_vec did not contain any Some(Selection)."
|
||||
))]
|
||||
MissingSelection {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
|
||||
NestedNodeMissingChild {
|
||||
node_id: MarkNodeId,
|
||||
children_ids: Vec<MarkNodeId>,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"NestedNodeRequired: required a Nested node at this position, node was a {}.",
|
||||
node_type
|
||||
))]
|
||||
NestedNodeRequired {
|
||||
node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("NestedNodeWithoutChildren: tried to retrieve child from Nested MarkupNode with id {} but it had no children.", node_id))]
|
||||
NestedNodeWithoutChildren {
|
||||
node_id: MarkNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("NoDefMarkNodeBeforeLineNr: I could not find a MarkupNode whose root parent points to a DefId located before the given line number: {}.", line_nr))]
|
||||
NoDefMarkNodeBeforeLineNr {
|
||||
line_nr: usize,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))]
|
||||
NodeWithoutAttributes {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"NodeIdNotInGridNodeMap: MarkNodeId {} was not found in ed_model.grid_node_map.",
|
||||
node_id
|
||||
))]
|
||||
NodeIdNotInGridNodeMap {
|
||||
node_id: MarkNodeId,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"NoNodeAtCaretPosition: there was no node at the current caret position {:?}.",
|
||||
caret_pos,
|
||||
))]
|
||||
NoNodeAtCaretPosition {
|
||||
caret_pos: TextPos,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"OutOfBounds: index {} was out of bounds for {} with length {}.",
|
||||
index,
|
||||
collection_name,
|
||||
len
|
||||
))]
|
||||
OutOfBounds {
|
||||
index: usize,
|
||||
collection_name: String,
|
||||
len: usize,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))]
|
||||
RecordWithoutFields {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"RocCheckFailed: `cargo run check`/`roc check` detected errors(see terminal)."
|
||||
))]
|
||||
RocCheckFailed,
|
||||
|
||||
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
|
||||
SrcParseError {
|
||||
syntax_err: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("StringParseError: {}", msg))]
|
||||
StringParseError {
|
||||
msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"UnexpectedASTNode: required a {} at this position, node was a {}.",
|
||||
required_node_type,
|
||||
encountered_node_type
|
||||
))]
|
||||
UnexpectedASTNode {
|
||||
required_node_type: String,
|
||||
encountered_node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.",
|
||||
descriptive_vec_name
|
||||
))]
|
||||
UnexpectedEmptyPoolVec {
|
||||
descriptive_vec_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.",
|
||||
required_pattern2,
|
||||
encountered_pattern2,
|
||||
))]
|
||||
UnexpectedPattern2Variant {
|
||||
required_pattern2: String,
|
||||
encountered_pattern2: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("ASTError: {}", msg))]
|
||||
ASTErrorBacktrace {
|
||||
msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("UIError: {}", msg))]
|
||||
UIErrorBacktrace {
|
||||
msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("MarkError: {}", msg))]
|
||||
MarkErrorBacktrace {
|
||||
msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
WrapASTError {
|
||||
#[snafu(backtrace)]
|
||||
source: ASTError,
|
||||
},
|
||||
WrapUIError {
|
||||
#[snafu(backtrace)]
|
||||
source: UIError,
|
||||
},
|
||||
WrapMarkError {
|
||||
#[snafu(backtrace)]
|
||||
source: MarkError,
|
||||
},
|
||||
WrapModuleError {
|
||||
#[snafu(backtrace)]
|
||||
source: ModuleError,
|
||||
},
|
||||
WrapIoError {
|
||||
source: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
pub type EdResult<T, E = EdError> = std::result::Result<T, E>;
|
||||
|
||||
pub fn print_err(err: &EdError) {
|
||||
eprintln!("{}", format!("{err}").truecolor(255, 0, 0));
|
||||
|
||||
if let Some(backtrace) = ErrorCompat::backtrace(err) {
|
||||
eprintln!("{}", color_backtrace(backtrace));
|
||||
}
|
||||
}
|
||||
|
||||
fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
|
||||
let backtrace_str = format!("{backtrace}");
|
||||
let backtrace_split = backtrace_str.split('\n');
|
||||
let irrelevant_src = vec![".cargo", "registry", ".rustup", "rustc"];
|
||||
|
||||
let mut ret_str = String::new();
|
||||
let mut prev_line_opt: Option<String> = None;
|
||||
|
||||
for line in backtrace_split {
|
||||
let new_line = if line.contains("src") {
|
||||
if !contains_one_of(line, &irrelevant_src) {
|
||||
if let Some(prev_line) = prev_line_opt {
|
||||
prev_line_opt = Some(format!("{}", prev_line.truecolor(255, 30, 30)));
|
||||
}
|
||||
format!("{}\n", line.truecolor(255, 100, 100))
|
||||
} else {
|
||||
format!("{line}\n")
|
||||
}
|
||||
} else {
|
||||
format!("{line}\n")
|
||||
};
|
||||
|
||||
if let Some(prev_line) = prev_line_opt {
|
||||
ret_str.push_str(&prev_line);
|
||||
}
|
||||
prev_line_opt = Some(new_line);
|
||||
}
|
||||
|
||||
ret_str
|
||||
}
|
||||
|
||||
fn contains_one_of(main_str: &str, contain_slice: &[&str]) -> bool {
|
||||
for contain_str in contain_slice {
|
||||
if main_str.contains(contain_str) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
impl From<EdError> for String {
|
||||
fn from(ed_error: EdError) -> Self {
|
||||
format!("{ed_error}")
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ui::ui_error::UIError;
|
||||
|
||||
impl From<UIError> for EdError {
|
||||
fn from(ui_err: UIError) -> Self {
|
||||
Self::WrapUIError { source: ui_err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MarkError> for EdError {
|
||||
fn from(mark_err: MarkError) -> Self {
|
||||
Self::WrapMarkError { source: mark_err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ASTError> for EdError {
|
||||
fn from(ast_err: ASTError) -> Self {
|
||||
Self::WrapASTError { source: ast_err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleError> for EdError {
|
||||
fn from(module_err: ModuleError) -> Self {
|
||||
Self::WrapModuleError { source: module_err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for EdError {
|
||||
fn from(io_err: std::io::Error) -> Self {
|
||||
Self::WrapIoError { source: io_err }
|
||||
}
|
||||
}
|
@ -1,443 +0,0 @@
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::ed_error::NestedNodeWithoutChildrenSnafu;
|
||||
use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNrSnafu, NodeIdNotInGridNodeMapSnafu};
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::editor::util::first_last_index_of;
|
||||
use crate::editor::util::index_of;
|
||||
use crate::ui::text::selection::Selection;
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
use crate::ui::ui_error::{LineInsertionFailedSnafu, OutOfBoundsSnafu, UIResult};
|
||||
use crate::ui::util::{slice_get, slice_get_mut};
|
||||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
use roc_code_markup::markup::mark_id_ast_id_map::MarkIdAstIdMap;
|
||||
use roc_code_markup::markup::nodes::get_root_mark_node_id;
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
use roc_code_markup::slow_pool::SlowPool;
|
||||
use snafu::OptionExt;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridNodeMap {
|
||||
pub lines: Vec<Vec<MarkNodeId>>,
|
||||
}
|
||||
|
||||
impl GridNodeMap {
|
||||
pub fn insert_between_line(
|
||||
&mut self,
|
||||
line_nr: usize,
|
||||
index: usize,
|
||||
len: usize,
|
||||
node_id: MarkNodeId,
|
||||
) -> UIResult<()> {
|
||||
let nr_of_lines = self.lines.len();
|
||||
|
||||
if line_nr < nr_of_lines {
|
||||
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
|
||||
let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
|
||||
|
||||
line_ref.splice(index..index, new_cols_vec);
|
||||
} else if line_nr >= nr_of_lines {
|
||||
for _ in 0..((line_nr - nr_of_lines) + 1) {
|
||||
self.push_empty_line();
|
||||
}
|
||||
|
||||
self.insert_between_line(line_nr, index, len, node_id)?;
|
||||
} else {
|
||||
LineInsertionFailedSnafu {
|
||||
line_nr,
|
||||
nr_of_lines,
|
||||
}
|
||||
.fail()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> {
|
||||
if line_nr <= self.lines.len() {
|
||||
self.lines.insert(line_nr, Vec::new());
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
OutOfBoundsSnafu {
|
||||
index: line_nr,
|
||||
collection_name: "code_lines.lines".to_owned(),
|
||||
len: self.lines.len(),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_empty_line(&mut self) {
|
||||
self.lines.push(vec![]);
|
||||
}
|
||||
|
||||
pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> {
|
||||
// clippy prefers this over if-else
|
||||
match line_nr.cmp(&self.lines.len()) {
|
||||
Ordering::Less => {
|
||||
self.insert_empty_line(line_nr + 1)?;
|
||||
|
||||
let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr
|
||||
|
||||
if col_nr < line_ref.len() {
|
||||
let next_line_str: Vec<MarkNodeId> = line_ref.drain(col_nr..).collect();
|
||||
|
||||
let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line
|
||||
|
||||
*next_line_ref = next_line_str;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Equal => self.insert_empty_line(line_nr + 1),
|
||||
Ordering::Greater => OutOfBoundsSnafu {
|
||||
index: line_nr,
|
||||
collection_name: "grid_node_map.lines".to_owned(),
|
||||
len: self.lines.len(),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
|
||||
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
|
||||
|
||||
*line_ref = vec![];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn del_line(&mut self, line_nr: usize) {
|
||||
self.lines.remove(line_nr);
|
||||
}
|
||||
|
||||
pub fn del_at_line(&mut self, line_nr: usize, column: usize) -> UIResult<()> {
|
||||
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
|
||||
|
||||
line_ref.remove(column);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn del_range_at_line(
|
||||
&mut self,
|
||||
line_nr: usize,
|
||||
col_range: std::ops::Range<usize>,
|
||||
) -> UIResult<()> {
|
||||
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
|
||||
|
||||
line_ref.drain(col_range);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
|
||||
if selection.is_on_same_line() {
|
||||
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
|
||||
|
||||
line_ref.drain(selection.start_pos.column..selection.end_pos.column);
|
||||
} else {
|
||||
unimplemented!("TODO support deleting multiline selection")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> {
|
||||
let line = slice_get(caret_pos.line, &self.lines)?;
|
||||
let node_id = slice_get(caret_pos.column, line)?;
|
||||
|
||||
Ok(*node_id)
|
||||
}
|
||||
|
||||
pub fn get_offset_to_node_id(
|
||||
&self,
|
||||
caret_pos: TextPos,
|
||||
node_id: MarkNodeId,
|
||||
) -> EdResult<usize> {
|
||||
let line = slice_get(caret_pos.line, &self.lines)?;
|
||||
|
||||
let first_node_index = index_of(node_id, line)?;
|
||||
|
||||
Ok(caret_pos.column - first_node_index)
|
||||
}
|
||||
|
||||
pub fn node_exists_at_pos(&self, pos: TextPos) -> bool {
|
||||
if pos.line < self.lines.len() {
|
||||
// safe unwrap because we checked the length
|
||||
let line = self.lines.get(pos.line).unwrap();
|
||||
|
||||
pos.column < line.len()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// get position of first occurrence of node_id if get_first_pos, else get the last occurrence
|
||||
pub fn get_node_position(&self, node_id: MarkNodeId, get_first_pos: bool) -> EdResult<TextPos> {
|
||||
let mut last_pos_opt = None;
|
||||
|
||||
for (line_index, line) in self.lines.iter().enumerate() {
|
||||
for (col_index, iter_node_id) in line.iter().enumerate() {
|
||||
if node_id == *iter_node_id && get_first_pos {
|
||||
return Ok(TextPos {
|
||||
line: line_index,
|
||||
column: col_index,
|
||||
});
|
||||
} else if node_id == *iter_node_id {
|
||||
last_pos_opt = Some(TextPos {
|
||||
line: line_index,
|
||||
column: col_index,
|
||||
})
|
||||
} else if let Some(last_pos) = last_pos_opt {
|
||||
return Ok(last_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last_pos) = last_pos_opt {
|
||||
Ok(last_pos)
|
||||
} else {
|
||||
NodeIdNotInGridNodeMapSnafu { node_id }.fail()
|
||||
}
|
||||
}
|
||||
|
||||
// returns start and end pos of Expr2/Def2, relevant AST node and MarkNodeId of the corresponding MarkupNode
|
||||
pub fn get_block_start_end_pos(
|
||||
&self,
|
||||
caret_pos: TextPos,
|
||||
ed_model: &EdModel,
|
||||
) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> {
|
||||
let line = slice_get(caret_pos.line, &self.lines)?;
|
||||
let node_id = *slice_get(caret_pos.column, line)?;
|
||||
let node = ed_model.mark_node_pool.get(node_id);
|
||||
|
||||
if node.is_nested() {
|
||||
let (start_pos, end_pos) = self.get_nested_start_end_pos(node_id, ed_model)?;
|
||||
|
||||
Ok((
|
||||
start_pos,
|
||||
end_pos,
|
||||
ed_model.mark_id_ast_id_map.get(node_id)?,
|
||||
node_id,
|
||||
))
|
||||
} else {
|
||||
let (first_node_index, last_node_index) = first_last_index_of(node_id, line)?;
|
||||
|
||||
let curr_node_id = *slice_get(first_node_index, line)?;
|
||||
let curr_ast_node_id = ed_model.mark_id_ast_id_map.get(curr_node_id)?;
|
||||
|
||||
let mut expr_start_index = first_node_index;
|
||||
let mut expr_end_index = last_node_index;
|
||||
|
||||
// we may encounter ast id's of children of the current node
|
||||
let mut pos_extra_subtract = 0;
|
||||
|
||||
for i in (0..first_node_index).rev() {
|
||||
let prev_pos_node_id = *slice_get(i, line)?;
|
||||
let prev_ast_node_id = ed_model.mark_id_ast_id_map.get(prev_pos_node_id)?;
|
||||
|
||||
if prev_ast_node_id == curr_ast_node_id {
|
||||
if pos_extra_subtract > 0 {
|
||||
expr_start_index -= pos_extra_subtract + 1;
|
||||
pos_extra_subtract = 0;
|
||||
} else {
|
||||
expr_start_index -= 1;
|
||||
}
|
||||
} else {
|
||||
pos_extra_subtract += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// we may encounter ast id's of children of the current node
|
||||
let mut pos_extra_add = 0;
|
||||
|
||||
for i in last_node_index..line.len() {
|
||||
let next_pos_node_id = slice_get(i, line)?;
|
||||
let next_ast_node_id = ed_model.mark_id_ast_id_map.get(*next_pos_node_id)?;
|
||||
|
||||
if next_ast_node_id == curr_ast_node_id {
|
||||
if pos_extra_add > 0 {
|
||||
expr_end_index += pos_extra_add + 1;
|
||||
pos_extra_add = 0;
|
||||
} else {
|
||||
expr_end_index += 1;
|
||||
}
|
||||
} else {
|
||||
pos_extra_add += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let correct_mark_node_id = GridNodeMap::get_top_node_with_expr_id(
|
||||
curr_node_id,
|
||||
&ed_model.mark_node_pool,
|
||||
&ed_model.mark_id_ast_id_map,
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
TextPos {
|
||||
line: caret_pos.line,
|
||||
column: expr_start_index,
|
||||
},
|
||||
TextPos {
|
||||
line: caret_pos.line,
|
||||
column: expr_end_index,
|
||||
},
|
||||
curr_ast_node_id,
|
||||
correct_mark_node_id,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// A markup node may refer to a bracket `{`, in that case we want the parent, a Nested MarkNode.
|
||||
// `{` is not the entire Expr2
|
||||
fn get_top_node_with_expr_id(
|
||||
curr_node_id: MarkNodeId,
|
||||
mark_node_pool: &SlowPool,
|
||||
mark_id_ast_id_map: &MarkIdAstIdMap,
|
||||
) -> EdResult<MarkNodeId> {
|
||||
let curr_node = mark_node_pool.get(curr_node_id);
|
||||
|
||||
if let Some(parent_id) = curr_node.get_parent_id_opt() {
|
||||
if mark_id_ast_id_map.get(parent_id)? == mark_id_ast_id_map.get(curr_node_id)? {
|
||||
Ok(parent_id)
|
||||
} else {
|
||||
Ok(curr_node_id)
|
||||
}
|
||||
} else {
|
||||
Ok(curr_node_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_nested_start_end_pos(
|
||||
&self,
|
||||
nested_node_id: MarkNodeId,
|
||||
ed_model: &EdModel,
|
||||
) -> EdResult<(TextPos, TextPos)> {
|
||||
let left_most_leaf = self.get_leftmost_leaf(nested_node_id, ed_model)?;
|
||||
|
||||
let right_most_leaf = self.get_rightmost_leaf(nested_node_id, ed_model)?;
|
||||
|
||||
let expr_start_pos = ed_model
|
||||
.grid_node_map
|
||||
.get_node_position(left_most_leaf, true)?;
|
||||
let expr_end_pos = ed_model
|
||||
.grid_node_map
|
||||
.get_node_position(right_most_leaf, false)?
|
||||
.increment_col();
|
||||
|
||||
Ok((expr_start_pos, expr_end_pos))
|
||||
}
|
||||
|
||||
fn get_leftmost_leaf(
|
||||
&self,
|
||||
nested_node_id: MarkNodeId,
|
||||
ed_model: &EdModel,
|
||||
) -> EdResult<MarkNodeId> {
|
||||
let mut children_ids = ed_model
|
||||
.mark_node_pool
|
||||
.get(nested_node_id)
|
||||
.get_children_ids();
|
||||
let mut first_child_id = 0;
|
||||
|
||||
while !children_ids.is_empty() {
|
||||
first_child_id =
|
||||
*children_ids
|
||||
.first()
|
||||
.with_context(|| NestedNodeWithoutChildrenSnafu {
|
||||
node_id: nested_node_id,
|
||||
})?;
|
||||
|
||||
children_ids = ed_model
|
||||
.mark_node_pool
|
||||
.get(first_child_id)
|
||||
.get_children_ids();
|
||||
}
|
||||
|
||||
Ok(first_child_id)
|
||||
}
|
||||
|
||||
fn get_rightmost_leaf(
|
||||
&self,
|
||||
nested_node_id: MarkNodeId,
|
||||
ed_model: &EdModel,
|
||||
) -> EdResult<MarkNodeId> {
|
||||
let mut children_ids = ed_model
|
||||
.mark_node_pool
|
||||
.get(nested_node_id)
|
||||
.get_children_ids();
|
||||
let mut last_child_id = 0;
|
||||
|
||||
while !children_ids.is_empty() {
|
||||
last_child_id =
|
||||
*children_ids
|
||||
.last()
|
||||
.with_context(|| NestedNodeWithoutChildrenSnafu {
|
||||
node_id: nested_node_id,
|
||||
})?;
|
||||
|
||||
children_ids = ed_model
|
||||
.mark_node_pool
|
||||
.get(last_child_id)
|
||||
.get_children_ids();
|
||||
}
|
||||
|
||||
Ok(last_child_id)
|
||||
}
|
||||
|
||||
// get id of root mark_node whose ast_node_id points to a DefId
|
||||
pub fn get_def_mark_node_id_before_line(
|
||||
&self,
|
||||
line_nr: usize,
|
||||
mark_node_pool: &SlowPool,
|
||||
mark_id_ast_id_map: &MarkIdAstIdMap,
|
||||
) -> EdResult<MarkNodeId> {
|
||||
for curr_line_nr in (0..line_nr).rev() {
|
||||
let first_col_pos = TextPos {
|
||||
line: curr_line_nr,
|
||||
column: 0,
|
||||
};
|
||||
|
||||
if self.node_exists_at_pos(first_col_pos) {
|
||||
let mark_node_id = self.get_id_at_row_col(first_col_pos)?;
|
||||
let root_mark_node_id = get_root_mark_node_id(mark_node_id, mark_node_pool);
|
||||
|
||||
let ast_node_id = mark_id_ast_id_map.get(root_mark_node_id)?;
|
||||
|
||||
if let ASTNodeId::ADefId(_) = ast_node_id {
|
||||
return Ok(root_mark_node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NoDefMarkNodeBeforeLineNrSnafu { line_nr }.fail()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GridNodeMap {
|
||||
fn default() -> Self {
|
||||
GridNodeMap {
|
||||
lines: vec![Vec::new()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GridNodeMap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for row in &self.lines {
|
||||
let row_str = row
|
||||
.iter()
|
||||
.map(|mark_node_id| format!(" {mark_node_id} "))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
writeln!(f, "{row_str}")?;
|
||||
}
|
||||
|
||||
writeln!(f, "(grid_node_map, {:?} lines)", self.lines.len())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::app_model::AppModel;
|
||||
use crate::editor::mvc::app_update::{
|
||||
handle_copy, handle_cut, handle_paste, pass_keydown_to_focused,
|
||||
};
|
||||
use crate::window::keyboard_input::from_winit;
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
||||
|
||||
pub fn handle_keydown(
|
||||
elem_state: ElementState,
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
modifiers_winit: ModifiersState,
|
||||
app_model: &mut AppModel,
|
||||
) -> EdResult<()> {
|
||||
if let ElementState::Released = elem_state {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let modifiers = from_winit(&modifiers_winit);
|
||||
|
||||
match virtual_keycode {
|
||||
Left | Up | Right | Down => {
|
||||
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?
|
||||
}
|
||||
|
||||
Copy => handle_copy(app_model)?,
|
||||
Paste => handle_paste(app_model)?,
|
||||
Cut => handle_cut(app_model)?,
|
||||
C => {
|
||||
if modifiers.cmd_or_ctrl() {
|
||||
handle_copy(app_model)?
|
||||
}
|
||||
}
|
||||
V => {
|
||||
if modifiers.cmd_or_ctrl() {
|
||||
handle_paste(app_model)?
|
||||
}
|
||||
}
|
||||
X => {
|
||||
if modifiers.cmd_or_ctrl() {
|
||||
handle_cut(app_model)?
|
||||
}
|
||||
}
|
||||
|
||||
_ => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn handle_text_input(
|
||||
// text_state: &mut String,
|
||||
// elem_state: ElementState,
|
||||
// virtual_keycode: VirtualKeyCode,
|
||||
// _modifiers: ModifiersState,
|
||||
// ) {
|
||||
// use winit::event::VirtualKeyCode::*;
|
||||
|
||||
// if let ElementState::Released = elem_state {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// match virtual_keycode {
|
||||
// Key1 | Numpad1 => text_state.push('1'),
|
||||
// Key2 | Numpad2 => text_state.push('2'),
|
||||
// Key3 | Numpad3 => text_state.push('3'),
|
||||
// Key4 | Numpad4 => text_state.push('4'),
|
||||
// Key5 | Numpad5 => text_state.push('5'),
|
||||
// Key6 | Numpad6 => text_state.push('6'),
|
||||
// Key7 | Numpad7 => text_state.push('7'),
|
||||
// Key8 | Numpad8 => text_state.push('8'),
|
||||
// Key9 | Numpad9 => text_state.push('9'),
|
||||
// Key0 | Numpad0 => text_state.push('0'),
|
||||
// A => text_state.push('a'),
|
||||
// B => text_state.push('b'),
|
||||
// C => text_state.push('c'),
|
||||
// D => text_state.push('d'),
|
||||
// E => text_state.push('e'),
|
||||
// F => text_state.push('f'),
|
||||
// G => text_state.push('g'),
|
||||
// H => text_state.push('h'),
|
||||
// I => text_state.push('i'),
|
||||
// J => text_state.push('j'),
|
||||
// K => text_state.push('k'),
|
||||
// L => text_state.push('l'),
|
||||
// M => text_state.push('m'),
|
||||
// N => text_state.push('n'),
|
||||
// O => text_state.push('o'),
|
||||
// P => text_state.push('p'),
|
||||
// Q => text_state.push('q'),
|
||||
// R => text_state.push('r'),
|
||||
// S => text_state.push('s'),
|
||||
// T => text_state.push('t'),
|
||||
// U => text_state.push('u'),
|
||||
// V => text_state.push('v'),
|
||||
// W => text_state.push('w'),
|
||||
// X => text_state.push('x'),
|
||||
// Y => text_state.push('y'),
|
||||
// Z => text_state.push('z'),
|
||||
// Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
|
||||
// | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
|
||||
// | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
|
||||
// | caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana
|
||||
// | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause
|
||||
// | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward
|
||||
// | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket
|
||||
// | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake
|
||||
// | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab
|
||||
// | WebStop => {
|
||||
// // TODO handle
|
||||
// }
|
||||
// Back => {
|
||||
// text_state.pop();
|
||||
// }
|
||||
// Return | NumpadEnter => {
|
||||
// text_state.push('\n');
|
||||
// }
|
||||
// Space => {
|
||||
// text_state.push(' ');
|
||||
// }
|
||||
// Comma | NumpadComma => {
|
||||
// text_state.push(',');
|
||||
// }
|
||||
// Add => {
|
||||
// text_state.push('+');
|
||||
// }
|
||||
// Apostrophe => {
|
||||
// text_state.push('\'');
|
||||
// }
|
||||
// At => {
|
||||
// text_state.push('@');
|
||||
// }
|
||||
// Backslash => {
|
||||
// text_state.push('\\');
|
||||
// }
|
||||
// Colon => {
|
||||
// text_state.push(':');
|
||||
// }
|
||||
// Period | Decimal => {
|
||||
// text_state.push('.');
|
||||
// }
|
||||
// Equals | NumpadEquals => {
|
||||
// text_state.push('=');
|
||||
// }
|
||||
// Grave => {
|
||||
// text_state.push('`');
|
||||
// }
|
||||
// Minus | Subtract => {
|
||||
// text_state.push('-');
|
||||
// }
|
||||
// Multiply => {
|
||||
// text_state.push('*');
|
||||
// }
|
||||
// Semicolon => {
|
||||
// text_state.push(';');
|
||||
// }
|
||||
// Slash | Divide => {
|
||||
// text_state.push('/');
|
||||
// }
|
||||
// Underline => {
|
||||
// text_state.push('_');
|
||||
// }
|
||||
// Yen => {
|
||||
// text_state.push('¥');
|
||||
// }
|
||||
// Copy => {
|
||||
// todo!("copy");
|
||||
// }
|
||||
// Paste => {
|
||||
// todo!("paste");
|
||||
// }
|
||||
// Cut => {
|
||||
// todo!("cut");
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,635 +0,0 @@
|
||||
use super::keyboard_input;
|
||||
use super::resources::strings::PLATFORM_DIR_NAME;
|
||||
use crate::editor::mvc::ed_view;
|
||||
use crate::editor::mvc::ed_view::RenderedWgpu;
|
||||
use crate::editor::resources::strings::{HELLO_WORLD, NOTHING_OPENED};
|
||||
use crate::editor::{
|
||||
config::Config,
|
||||
ed_error::print_err,
|
||||
mvc::{app_model::AppModel, app_update, app_update::InputOutcome, ed_model},
|
||||
theme::EdTheme,
|
||||
};
|
||||
use crate::graphics::{
|
||||
colors::to_wgpu_color,
|
||||
lowlevel::buffer::create_rect_buffers,
|
||||
lowlevel::ortho::update_ortho_buffer,
|
||||
lowlevel::pipelines,
|
||||
primitives::rect::Rect,
|
||||
primitives::text::{build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text},
|
||||
};
|
||||
use crate::ui::text::caret_w_select::CaretPos;
|
||||
use bumpalo::Bump;
|
||||
use cgmath::Vector2;
|
||||
use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue};
|
||||
use futures::TryFutureExt;
|
||||
use pipelines::RectResources;
|
||||
use roc_ast::lang::env::Env;
|
||||
use roc_ast::mem_pool::pool::Pool;
|
||||
use roc_ast::module::load_module;
|
||||
use roc_load::Threading;
|
||||
use roc_module::symbol::IdentIds;
|
||||
use roc_packaging::cache::{self, RocCacheDir};
|
||||
use roc_types::subs::VarStore;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs::{self, metadata, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{error::Error, io, path::Path};
|
||||
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
|
||||
use wgpu_glyph::GlyphBrush;
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event,
|
||||
event::{Event, ModifiersState},
|
||||
event_loop::ControlFlow,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
};
|
||||
|
||||
// Inspired by:
|
||||
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license
|
||||
// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license
|
||||
//
|
||||
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
|
||||
|
||||
/// The editor is actually launched from the CLI if you pass it zero arguments,
|
||||
/// or if you provide it 1 or more files or directories to open on launch.
|
||||
pub fn launch(project_path_opt: Option<&Path>) -> io::Result<()> {
|
||||
run_event_loop(project_path_opt).expect("Error running event loop");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_event_loop(project_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
// Open window and create a surface
|
||||
let mut event_loop = winit::event_loop::EventLoop::new();
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(1900.0, 1000.0))
|
||||
.with_title("The Roc Editor - Work In Progress")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
// Initialize GPU
|
||||
let (gpu_device, cmd_queue, color_format) = futures::executor::block_on(async {
|
||||
create_device(
|
||||
&instance,
|
||||
&surface,
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
false,
|
||||
)
|
||||
.or_else(|_| create_device(&instance, &surface, wgpu::PowerPreference::LowPower, false))
|
||||
.or_else(|_| {
|
||||
create_device(
|
||||
&instance,
|
||||
&surface,
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
true,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Failed to request device: `{err}`");
|
||||
})
|
||||
.await
|
||||
});
|
||||
|
||||
// Create staging belt and a local pool
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
let local_spawner = local_pool.spawner();
|
||||
|
||||
let mut size = window.inner_size();
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: color_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
};
|
||||
|
||||
surface.configure(&gpu_device, &surface_config);
|
||||
|
||||
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
|
||||
|
||||
let mut glyph_brush = build_glyph_brush(&gpu_device, color_format)?;
|
||||
|
||||
let is_animating = true;
|
||||
|
||||
let mut env_pool = Pool::with_capacity(1024);
|
||||
let env_arena = Bump::new();
|
||||
let code_arena = Bump::new();
|
||||
|
||||
let (file_path_buf, code_str) = read_main_roc_file(project_path_opt);
|
||||
println!("Loading file {file_path_buf:?}...");
|
||||
|
||||
let file_path = Path::new(&file_path_buf);
|
||||
let loaded_module = load_module(
|
||||
file_path,
|
||||
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
let dep_idents = IdentIds::exposed_builtins(8);
|
||||
let exposed_ident_ids = IdentIds::default();
|
||||
let module_ids = loaded_module.interns.module_ids.clone();
|
||||
|
||||
let env = Env::new(
|
||||
loaded_module.module_id,
|
||||
&env_arena,
|
||||
&mut env_pool,
|
||||
&mut var_store,
|
||||
dep_idents,
|
||||
&module_ids,
|
||||
exposed_ident_ids,
|
||||
);
|
||||
|
||||
let config: Config = Config::default(); //confy::load("roc_editor", None)?;
|
||||
let ed_model_opt = {
|
||||
let ed_model_res = ed_model::init_model(
|
||||
&code_str,
|
||||
file_path,
|
||||
env,
|
||||
loaded_module,
|
||||
&code_arena,
|
||||
CaretPos::End,
|
||||
);
|
||||
|
||||
match ed_model_res {
|
||||
Ok(mut ed_model) => {
|
||||
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(
|
||||
&mut glyph_brush,
|
||||
config.code_font_size,
|
||||
));
|
||||
|
||||
Some(ed_model)
|
||||
}
|
||||
Err(e) => {
|
||||
print_err(&e);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut rendered_wgpu_opt: Option<RenderedWgpu> = None;
|
||||
|
||||
let mut app_model = AppModel::init(ed_model_opt);
|
||||
|
||||
let mut keyboard_modifiers = ModifiersState::empty();
|
||||
let ed_theme = EdTheme::default();
|
||||
|
||||
// Render loop
|
||||
window.request_redraw();
|
||||
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
// TODO dynamically switch this on/off depending on whether any
|
||||
// animations are running. Should conserve CPU usage and battery life!
|
||||
if is_animating {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
} else {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
|
||||
match event {
|
||||
//Close
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
//Resize
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
size = new_size;
|
||||
|
||||
surface.configure(
|
||||
&gpu_device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: color_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
},
|
||||
);
|
||||
|
||||
update_ortho_buffer(
|
||||
size.width,
|
||||
size.height,
|
||||
&gpu_device,
|
||||
&rect_resources.ortho.buffer,
|
||||
&cmd_queue,
|
||||
);
|
||||
}
|
||||
//Received Character
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
let input_outcome_res =
|
||||
app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers);
|
||||
if let Err(e) = input_outcome_res {
|
||||
print_err(&e)
|
||||
} else if let Ok(InputOutcome::Ignored) = input_outcome_res {
|
||||
println!("\nInput '{ch}' ignored!");
|
||||
} else {
|
||||
window.request_redraw()
|
||||
}
|
||||
}
|
||||
//Keyboard Input
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => {
|
||||
if let Some(virtual_keycode) = input.virtual_keycode {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
let keydown_res = keyboard_input::handle_keydown(
|
||||
input.state,
|
||||
virtual_keycode,
|
||||
keyboard_modifiers,
|
||||
&mut app_model,
|
||||
);
|
||||
|
||||
if let Err(e) = keydown_res {
|
||||
print_err(&e)
|
||||
}
|
||||
|
||||
window.request_redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Modifiers Changed
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ModifiersChanged(modifiers),
|
||||
..
|
||||
} => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::RedrawRequested { .. } => {
|
||||
// Get a command encoder for the current frame
|
||||
let mut encoder =
|
||||
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Redraw"),
|
||||
});
|
||||
|
||||
let surface_texture = surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next SwapChainTexture");
|
||||
|
||||
let view = surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if rendered_wgpu_opt.is_none() || ed_model.dirty {
|
||||
let rendered_wgpu_res = ed_view::model_to_wgpu(
|
||||
ed_model,
|
||||
&size,
|
||||
config.make_code_txt_xy().into(),
|
||||
&config,
|
||||
);
|
||||
|
||||
match rendered_wgpu_res {
|
||||
Ok(rendered_wgpu) => rendered_wgpu_opt = Some(rendered_wgpu),
|
||||
Err(e) => print_err(&e),
|
||||
}
|
||||
|
||||
ed_model.dirty = false;
|
||||
}
|
||||
|
||||
if let Some(ref rendered_wgpu) = rendered_wgpu_opt {
|
||||
draw_rects(
|
||||
&rendered_wgpu.rects_behind,
|
||||
&mut encoder,
|
||||
&view,
|
||||
&gpu_device,
|
||||
&rect_resources,
|
||||
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
|
||||
);
|
||||
|
||||
for text_section in &rendered_wgpu.text_sections_behind {
|
||||
let borrowed_text = text_section.to_borrowed();
|
||||
|
||||
glyph_brush.queue(borrowed_text);
|
||||
}
|
||||
|
||||
// draw first layer of text
|
||||
glyph_brush
|
||||
.draw_queued(
|
||||
&gpu_device,
|
||||
&mut staging_belt,
|
||||
&mut encoder,
|
||||
&view,
|
||||
size.width,
|
||||
size.height,
|
||||
)
|
||||
.expect("Failed to draw first layer of text.");
|
||||
|
||||
// draw rects on top of first text layer
|
||||
draw_rects(
|
||||
&rendered_wgpu.rects_front,
|
||||
&mut encoder,
|
||||
&view,
|
||||
&gpu_device,
|
||||
&rect_resources,
|
||||
wgpu::LoadOp::Load,
|
||||
);
|
||||
|
||||
for text_section in &rendered_wgpu.text_sections_front {
|
||||
let borrowed_text = text_section.to_borrowed();
|
||||
|
||||
glyph_brush.queue(borrowed_text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
begin_render_pass(
|
||||
&mut encoder,
|
||||
&view,
|
||||
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
|
||||
);
|
||||
|
||||
queue_no_file_text(
|
||||
&size,
|
||||
NOTHING_OPENED,
|
||||
config.make_code_txt_xy().into(),
|
||||
&config,
|
||||
&mut glyph_brush,
|
||||
);
|
||||
}
|
||||
|
||||
// draw text
|
||||
glyph_brush
|
||||
.draw_queued(
|
||||
&gpu_device,
|
||||
&mut staging_belt,
|
||||
&mut encoder,
|
||||
&view,
|
||||
size.width,
|
||||
size.height,
|
||||
)
|
||||
.expect("Failed to draw queued text.");
|
||||
|
||||
staging_belt.finish();
|
||||
cmd_queue.submit(Some(encoder.finish()));
|
||||
surface_texture.present();
|
||||
|
||||
// Recall unused staging buffers
|
||||
use futures::task::SpawnExt;
|
||||
|
||||
local_spawner
|
||||
.spawn(staging_belt.recall())
|
||||
.expect("Recall staging belt");
|
||||
|
||||
local_pool.run_until_stalled();
|
||||
}
|
||||
_ => {
|
||||
*control_flow = winit::event_loop::ControlFlow::Wait;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_device(
|
||||
instance: &wgpu::Instance,
|
||||
surface: &wgpu::Surface,
|
||||
power_preference: wgpu::PowerPreference,
|
||||
force_fallback_adapter: bool,
|
||||
) -> Result<(wgpu::Device, wgpu::Queue, wgpu::TextureFormat), wgpu::RequestDeviceError> {
|
||||
if force_fallback_adapter {
|
||||
log::error!("Falling back to software renderer. GPU acceleration has been disabled.");
|
||||
}
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference,
|
||||
compatible_surface: Some(surface),
|
||||
force_fallback_adapter,
|
||||
})
|
||||
.await
|
||||
.expect(r#"Request adapter
|
||||
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md#editor
|
||||
"#);
|
||||
|
||||
let color_format = surface.get_preferred_format(&adapter).unwrap();
|
||||
|
||||
if color_format != wgpu::TextureFormat::Bgra8UnormSrgb {
|
||||
log::warn!("Your preferred TextureFormat {:?} is different than expected. Colors may look different, please report this issue on github and tag @Anton-4.", color_format);
|
||||
}
|
||||
|
||||
let request_res = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
match request_res {
|
||||
Ok((device, queue)) => Ok((device, queue, color_format)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_rects(
|
||||
all_rects: &[Rect],
|
||||
encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) {
|
||||
let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects);
|
||||
|
||||
let mut render_pass = begin_render_pass(encoder, texture_view, load_op);
|
||||
|
||||
render_pass.set_pipeline(&rect_resources.pipeline);
|
||||
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
|
||||
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
|
||||
render_pass.set_index_buffer(
|
||||
rect_buffers.index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
|
||||
}
|
||||
|
||||
fn begin_render_pass<'a>(
|
||||
encoder: &'a mut CommandEncoder,
|
||||
texture_view: &'a TextureView,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) -> RenderPass<'a> {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: texture_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: load_op,
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
|
||||
const ROC_PROJECTS_FOLDER: &str = "roc-projects";
|
||||
const ROC_NEW_PROJECT_FOLDER: &str = "new-roc-project-1";
|
||||
|
||||
fn read_main_roc_file(project_path_opt: Option<&Path>) -> (PathBuf, String) {
|
||||
if let Some(project_path) = project_path_opt {
|
||||
let path_metadata = metadata(project_path).unwrap_or_else(|err| panic!("You provided the path {:?}, but I could not read the metadata for the provided path; error: {:?}", &project_path, err));
|
||||
|
||||
if path_metadata.is_file() {
|
||||
let file_content_as_str = std::fs::read_to_string(project_path).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"You provided the file {:?}, but I could not read it; error: {}",
|
||||
&project_path, err
|
||||
)
|
||||
});
|
||||
|
||||
return (project_path.to_path_buf(), file_content_as_str);
|
||||
}
|
||||
|
||||
let mut ls_config = HashSet::new();
|
||||
ls_config.insert(DirEntryAttr::FullName);
|
||||
|
||||
let dir_items = ls(project_path, &ls_config)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Failed to list items in project directory; error: {err:?}")
|
||||
})
|
||||
.items;
|
||||
|
||||
let file_names = dir_items.iter().flat_map(|info_hash_map| {
|
||||
info_hash_map
|
||||
.values()
|
||||
.filter_map(|dir_entry_value| {
|
||||
if let DirEntryValue::String(file_name) = dir_entry_value {
|
||||
Some(file_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&String>>()
|
||||
});
|
||||
|
||||
let roc_file_names: Vec<&String> = file_names
|
||||
.filter(|file_name| file_name.contains(".roc"))
|
||||
.collect();
|
||||
|
||||
if let Some(&roc_file_name) = roc_file_names.first() {
|
||||
let full_roc_file_path = project_path.join(roc_file_name);
|
||||
let file_content_as_str = std::fs::read_to_string(Path::new(&full_roc_file_path))
|
||||
.unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {:?}, but I failed to read it: {}", &project_path, full_roc_file_path, err));
|
||||
|
||||
(full_roc_file_path, file_content_as_str)
|
||||
} else {
|
||||
init_new_roc_project(project_path)
|
||||
}
|
||||
} else {
|
||||
init_new_roc_project(&Path::new(ROC_PROJECTS_FOLDER).join(ROC_NEW_PROJECT_FOLDER))
|
||||
}
|
||||
}
|
||||
|
||||
// returns path and content of app file
|
||||
fn init_new_roc_project(project_dir_path: &Path) -> (PathBuf, String) {
|
||||
let orig_platform_path = Path::new("examples")
|
||||
.join("platform-switching")
|
||||
.join(PLATFORM_DIR_NAME);
|
||||
|
||||
let roc_file_path = Path::new(project_dir_path).join("main.roc");
|
||||
|
||||
let project_platform_path = project_dir_path.join(PLATFORM_DIR_NAME);
|
||||
|
||||
if !project_dir_path.exists() {
|
||||
fs::create_dir_all(project_dir_path).expect("Failed to create dir for roc project.");
|
||||
}
|
||||
|
||||
copy_roc_platform_if_not_exists(
|
||||
&orig_platform_path,
|
||||
&project_platform_path,
|
||||
project_dir_path,
|
||||
);
|
||||
|
||||
let code_str = create_roc_file_if_not_exists(project_dir_path, &roc_file_path);
|
||||
|
||||
(roc_file_path, code_str)
|
||||
}
|
||||
|
||||
// returns contents of file
|
||||
fn create_roc_file_if_not_exists(project_dir_path: &Path, roc_file_path: &Path) -> String {
|
||||
if !roc_file_path.exists() {
|
||||
let mut roc_file = File::create(roc_file_path).unwrap_or_else(|err| {
|
||||
panic!("No roc file path was passed to the editor, so I wanted to create a new roc project with the file {roc_file_path:?}, but it failed: {err}")
|
||||
});
|
||||
|
||||
write!(roc_file, "{HELLO_WORLD}").unwrap_or_else(|err| {
|
||||
panic!(
|
||||
r#"No roc file path was passed to the editor, so I created a new roc project with the file {roc_file_path:?}
|
||||
I wanted to write roc hello world to that file, but it failed: {err:?}"#
|
||||
)
|
||||
});
|
||||
|
||||
HELLO_WORLD.to_string()
|
||||
} else {
|
||||
std::fs::read_to_string(roc_file_path).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"I detected an existing {roc_file_path:?} inside {project_dir_path:?}, but I failed to read from it: {err}"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_roc_platform_if_not_exists(
|
||||
orig_platform_path: &Path,
|
||||
project_platform_path: &Path,
|
||||
project_dir_path: &Path,
|
||||
) {
|
||||
if !orig_platform_path.exists() && !project_platform_path.exists() {
|
||||
panic!(
|
||||
r#"No roc file path was passed to the editor, so I wanted to create a new roc project but I could not find the platform at {:?}.
|
||||
Are you at the root of the roc repository?
|
||||
My current directory is: {:?}"#,
|
||||
orig_platform_path,
|
||||
env::current_dir()
|
||||
);
|
||||
} else if !project_platform_path.exists() {
|
||||
copy(orig_platform_path, project_dir_path, &CopyOptions::new()).unwrap_or_else(|err|{
|
||||
panic!(r#"No roc file path was passed to the editor, so I wanted to create a new roc project and roc projects require a platform,
|
||||
I tried to copy the platform at {orig_platform_path:?} to {project_platform_path:?} but it failed: {err}"#
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_no_file_text(
|
||||
size: &PhysicalSize<u32>,
|
||||
text: &str,
|
||||
text_coords: Vector2<f32>,
|
||||
config: &Config,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
) {
|
||||
let area_bounds = (size.width as f32, size.height as f32).into();
|
||||
|
||||
let code_text = Text {
|
||||
position: text_coords,
|
||||
area_bounds,
|
||||
color: config.ed_theme.ui_theme.text,
|
||||
text,
|
||||
size: config.code_font_size,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
queue_text_draw(&code_text, glyph_brush);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mod code_lines;
|
||||
mod config;
|
||||
pub mod ed_error;
|
||||
mod grid_node_map;
|
||||
mod keyboard_input;
|
||||
pub mod main;
|
||||
mod mvc;
|
||||
mod render_ast;
|
||||
mod render_debug;
|
||||
mod resources;
|
||||
mod theme;
|
||||
mod util;
|
@ -1,108 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::ed_model::EdModel;
|
||||
use crate::editor::ed_error::{
|
||||
print_err,
|
||||
EdError::{ClipboardInitFailed, ClipboardReadFailed, ClipboardWriteFailed},
|
||||
EdResult,
|
||||
};
|
||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
use std::fmt;
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
pub struct AppModel<'a> {
|
||||
pub ed_model_opt: Option<EdModel<'a>>,
|
||||
pub clipboard_opt: Option<Clipboard>,
|
||||
pub sound_thread_pool: ThreadPool, // thread is blocked while sound is played, hence the threadpool
|
||||
}
|
||||
|
||||
impl<'a> AppModel<'a> {
|
||||
pub fn init(ed_model_opt: Option<EdModel<'a>>) -> AppModel {
|
||||
AppModel {
|
||||
ed_model_opt,
|
||||
clipboard_opt: AppModel::init_clipboard_opt(),
|
||||
sound_thread_pool: ThreadPool::new(7), // can play up to 7 sounds simultaneously
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_clipboard_opt() -> Option<Clipboard> {
|
||||
let clipboard_res = Clipboard::init();
|
||||
|
||||
match clipboard_res {
|
||||
Ok(clipboard) => Some(clipboard),
|
||||
Err(e) => {
|
||||
print_err(&e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Clipboard {
|
||||
context: ClipboardContext,
|
||||
}
|
||||
|
||||
impl Clipboard {
|
||||
pub fn init() -> EdResult<Clipboard> {
|
||||
let context_res = ClipboardContext::new();
|
||||
|
||||
match context_res {
|
||||
Ok(context) => Ok(Clipboard { context }),
|
||||
Err(e) => Err(ClipboardInitFailed {
|
||||
err_msg: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// clipboard crate needs this to be mutable
|
||||
pub fn get_content(&mut self) -> EdResult<String> {
|
||||
let content_res = self.context.get_contents();
|
||||
|
||||
match content_res {
|
||||
Ok(content_str) => Ok(content_str),
|
||||
Err(e) => Err(ClipboardReadFailed {
|
||||
err_msg: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, copy_str: String) -> EdResult<()> {
|
||||
let content_set_res = self.context.set_contents(copy_str);
|
||||
|
||||
match content_set_res {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(ClipboardWriteFailed {
|
||||
err_msg: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_clipboard_txt(clipboard_opt: &mut Option<Clipboard>, txt: &str) -> EdResult<()> {
|
||||
if let Some(ref mut clipboard) = clipboard_opt {
|
||||
clipboard.set_content(txt.to_owned())?;
|
||||
} else {
|
||||
return Err(ClipboardWriteFailed {
|
||||
err_msg: "Clipboard was never initialized successfully.".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_clipboard_txt(clipboard_opt: &mut Option<Clipboard>) -> EdResult<String> {
|
||||
if let Some(ref mut clipboard) = clipboard_opt {
|
||||
clipboard.get_content()
|
||||
} else {
|
||||
Err(ClipboardReadFailed {
|
||||
err_msg: "Clipboard was never initialized successfully.".to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Clipboard {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// showing the clipboard would require a mut ref which is not possible
|
||||
f.debug_struct("Clipboard (can't show)").finish()
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
use super::app_model::AppModel;
|
||||
use super::ed_update;
|
||||
use crate::window::keyboard_input::Modifiers;
|
||||
use crate::{editor::ed_error::EdResult, window::keyboard_input::from_winit};
|
||||
use winit::event::{ModifiersState, VirtualKeyCode};
|
||||
|
||||
pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
unimplemented!("TODO");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_paste(app_model: &mut AppModel) -> EdResult<()> {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
unimplemented!("TODO");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_cut(app_model: &mut AppModel) -> EdResult<()> {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
unimplemented!("TODO");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pass_keydown_to_focused(
|
||||
modifiers: &Modifiers,
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
app_model: &mut AppModel,
|
||||
) -> EdResult<()> {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
ed_model.ed_handle_key_down(
|
||||
modifiers,
|
||||
virtual_keycode,
|
||||
&mut app_model.sound_thread_pool,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputOutcome {
|
||||
Accepted,
|
||||
Ignored,
|
||||
SilentIgnored,
|
||||
}
|
||||
|
||||
pub fn handle_new_char(
|
||||
received_char: &char,
|
||||
app_model: &mut AppModel,
|
||||
modifiers_winit: ModifiersState,
|
||||
) -> EdResult<InputOutcome> {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
let modifiers = from_winit(&modifiers_winit);
|
||||
|
||||
if modifiers.new_char_modifiers() {
|
||||
// shortcuts with modifiers are handled by ed_handle_key_down
|
||||
return ed_update::handle_new_char(received_char, ed_model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InputOutcome::SilentIgnored)
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
pub mod test_app_update {
|
||||
use crate::editor::mvc::app_model;
|
||||
use crate::editor::mvc::app_model::{AppModel, Clipboard};
|
||||
use crate::editor::mvc::app_update::{handle_copy, handle_cut, handle_paste};
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::ui::text::{
|
||||
big_selectable_text::test_big_sel_text::{
|
||||
all_lines_vec, convert_selection_to_dsl, gen_big_text,
|
||||
},
|
||||
big_selectable_text::BigSelectableText,
|
||||
};
|
||||
|
||||
pub fn mock_app_model(
|
||||
big_sel_text: BigSelectableText,
|
||||
clipboard_opt: Option<Clipboard>,
|
||||
) -> AppModel {
|
||||
AppModel {
|
||||
ed_model_opt: Some(EdModel {
|
||||
text: big_sel_text,
|
||||
glyph_dim_rect_opt: None,
|
||||
has_focus: true,
|
||||
}),
|
||||
clipboard_opt,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_copy(
|
||||
pre_lines_str: &[&str],
|
||||
expected_clipboard_content: &str,
|
||||
clipboard_opt: Option<Clipboard>,
|
||||
) -> Result<Option<Clipboard>, String> {
|
||||
let pre_text_buf = gen_big_text(pre_lines_str)?;
|
||||
|
||||
let mut app_model = mock_app_model(pre_text_buf, clipboard_opt);
|
||||
|
||||
handle_copy(&mut app_model)?;
|
||||
|
||||
let clipboard_content = app_model::get_clipboard_txt(&mut app_model.clipboard_opt)?;
|
||||
|
||||
assert_eq!(clipboard_content, expected_clipboard_content);
|
||||
|
||||
Ok(app_model.clipboard_opt)
|
||||
}
|
||||
|
||||
fn assert_paste(
|
||||
pre_lines_str: &[&str],
|
||||
clipboard_content: &str,
|
||||
expected_post_lines_str: &[&str],
|
||||
clipboard_opt: Option<Clipboard>,
|
||||
) -> Result<Option<Clipboard>, String> {
|
||||
let pre_big_text = gen_big_text(pre_lines_str)?;
|
||||
|
||||
let mut app_model = mock_app_model(pre_big_text, clipboard_opt);
|
||||
|
||||
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, clipboard_content)?;
|
||||
|
||||
handle_paste(&mut app_model)?;
|
||||
|
||||
let ed_model = app_model.ed_model_opt.unwrap();
|
||||
let mut text_lines = all_lines_vec(&ed_model.text);
|
||||
let post_lines_str =
|
||||
convert_selection_to_dsl(ed_model.text.caret_w_select, &mut text_lines)?;
|
||||
|
||||
assert_eq!(post_lines_str, expected_post_lines_str);
|
||||
|
||||
Ok(app_model.clipboard_opt)
|
||||
}
|
||||
|
||||
fn assert_cut(
|
||||
pre_lines_str: &[&str],
|
||||
expected_clipboard_content: &str,
|
||||
expected_post_lines_str: &[&str],
|
||||
clipboard_opt: Option<Clipboard>,
|
||||
) -> Result<Option<Clipboard>, String> {
|
||||
let pre_big_text = gen_big_text(pre_lines_str)?;
|
||||
|
||||
let mut app_model = mock_app_model(pre_big_text, clipboard_opt);
|
||||
|
||||
handle_cut(&mut app_model)?;
|
||||
|
||||
let clipboard_content = app_model::get_clipboard_txt(&mut app_model.clipboard_opt)?;
|
||||
|
||||
assert_eq!(clipboard_content, expected_clipboard_content);
|
||||
|
||||
let ed_model = app_model.ed_model_opt.unwrap();
|
||||
let mut text_lines = all_lines_vec(&ed_model.text);
|
||||
let post_lines_str =
|
||||
convert_selection_to_dsl(ed_model.text.caret_w_select, &mut text_lines)?;
|
||||
|
||||
assert_eq!(post_lines_str, expected_post_lines_str);
|
||||
|
||||
Ok(app_model.clipboard_opt)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // ignored because of clipboard problems on ci
|
||||
fn copy_paste_cut() -> Result<(), String> {
|
||||
// can only init clipboard once
|
||||
let mut clipboard_opt = AppModel::init_clipboard_opt();
|
||||
|
||||
// copy
|
||||
clipboard_opt = assert_copy(&["[a]|"], "a", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(&["|[b]"], "b", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(&["a[ ]|"], " ", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(&["[ ]|b"], " ", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(&["a\n", "[b\n", "]|"], "b\n", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(&["[a\n", " b\n", "]|"], "a\n b\n", clipboard_opt)?;
|
||||
clipboard_opt = assert_copy(
|
||||
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
||||
"ef\nghi",
|
||||
clipboard_opt,
|
||||
)?;
|
||||
|
||||
// paste
|
||||
|
||||
clipboard_opt = assert_paste(&["|"], "", &["|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["|"], "a", &["a|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["a|"], "b", &["ab|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["|a"], "b", &["b|a"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["[a]|"], "c", &["c|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["[ab]|"], "d", &["d|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["a[b]|c"], "e", &["ae|c"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(&["a\n", "[b\n", "]|"], "f", &["a\n", "f|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_paste(
|
||||
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
||||
"ef\nghi",
|
||||
&["abc\n", "def\n", "ghi|\n", "jkl"],
|
||||
clipboard_opt,
|
||||
)?;
|
||||
|
||||
// cut
|
||||
clipboard_opt = assert_cut(&["[a]|"], "a", &["|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_cut(&["|[b]"], "b", &["|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_cut(&["a[ ]|"], " ", &["a|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_cut(&["[ ]|b"], " ", &["|b"], clipboard_opt)?;
|
||||
clipboard_opt = assert_cut(&["a\n", "[b\n", "]|"], "b\n", &["a\n", "|"], clipboard_opt)?;
|
||||
clipboard_opt = assert_cut(&["[a\n", " b\n", "]|"], "a\n b\n", &["|"], clipboard_opt)?;
|
||||
assert_cut(
|
||||
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
||||
"ef\nghi",
|
||||
&["abc\n", "d|\n", "jkl"],
|
||||
clipboard_opt,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}*/
|
@ -1,63 +0,0 @@
|
||||
use roc_ast::lang::core::def::def2::Def2;
|
||||
use roc_code_markup::markup::common_nodes::NEW_LINES_AFTER_DEF;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::app_update::InputOutcome;
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::editor::util::index_of;
|
||||
use crate::ui::text::lines::Lines;
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
|
||||
// put everything after caret on new line, create a Def2::Blank if there was nothing after the caret.
|
||||
pub fn break_line(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
|
||||
let carets = ed_model.get_carets();
|
||||
|
||||
for caret_pos in carets.iter() {
|
||||
let caret_line_nr = caret_pos.line;
|
||||
|
||||
// don't allow adding new lines on empty line
|
||||
if caret_pos.column > 0
|
||||
&& ed_model.grid_node_map.node_exists_at_pos(TextPos {
|
||||
line: caret_line_nr,
|
||||
column: caret_pos.column - 1,
|
||||
})
|
||||
{
|
||||
let new_blank_line_nr = caret_line_nr + NEW_LINES_AFTER_DEF;
|
||||
// if there already is a blank line at new_blank_line_nr just move the caret there, don't add extra lines
|
||||
// safe unwrap, we already checked the nr_of_lines
|
||||
if !(ed_model.code_lines.nr_of_lines() >= new_blank_line_nr
|
||||
&& ed_model.code_lines.line_len(new_blank_line_nr).unwrap() == 0)
|
||||
{
|
||||
for i in 1..=NEW_LINES_AFTER_DEF {
|
||||
EdModel::insert_empty_line(caret_line_nr + i, &mut ed_model.grid_node_map)?;
|
||||
}
|
||||
|
||||
insert_new_blank(ed_model, caret_pos.line + NEW_LINES_AFTER_DEF + 1)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ed_model.simple_move_carets_down(NEW_LINES_AFTER_DEF); // one blank lines between top level definitions
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
}
|
||||
|
||||
pub fn insert_new_blank(ed_model: &mut EdModel, insert_on_line_nr: usize) -> EdResult<()> {
|
||||
// find position of the previous ASTNode to figure out where to add this new Blank ASTNode
|
||||
let def_mark_node_id = ed_model.grid_node_map.get_def_mark_node_id_before_line(
|
||||
insert_on_line_nr,
|
||||
&ed_model.mark_node_pool,
|
||||
&ed_model.mark_id_ast_id_map,
|
||||
)?;
|
||||
|
||||
let new_line_blank = Def2::Blank;
|
||||
let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank);
|
||||
|
||||
let insertion_index = index_of(def_mark_node_id, &ed_model.markup_ids)?;
|
||||
ed_model
|
||||
.module
|
||||
.ast
|
||||
.insert_def_at_index(new_line_blank_id, insertion_index);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,362 +0,0 @@
|
||||
use crate::editor::code_lines::CodeLines;
|
||||
use crate::editor::grid_node_map::GridNodeMap;
|
||||
use crate::editor::{
|
||||
ed_error::SrcParseSnafu,
|
||||
ed_error::{EdResult, EmptyCodeStringSnafu, MissingParentSnafu, NoNodeAtCaretPositionSnafu},
|
||||
};
|
||||
use crate::graphics::primitives::rect::Rect;
|
||||
use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect};
|
||||
use crate::ui::text::lines::SelectableLines;
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
use crate::ui::ui_error::UIResult;
|
||||
use bumpalo::Bump;
|
||||
use nonempty::NonEmpty;
|
||||
use roc_ast::lang::core::ast::{ASTNodeId, AST};
|
||||
use roc_ast::lang::env::Env;
|
||||
use roc_ast::mem_pool::pool_str::PoolStr;
|
||||
use roc_ast::parse::parse_ast;
|
||||
use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes;
|
||||
use roc_code_markup::markup::mark_id_ast_id_map::MarkIdAstIdMap;
|
||||
use roc_code_markup::markup::nodes;
|
||||
use roc_code_markup::slow_pool::{MarkNodeId, SlowPool};
|
||||
use roc_load::LoadedModule;
|
||||
use roc_module::symbol::Interns;
|
||||
use std::path::Path;
|
||||
|
||||
/// Contains nearly all state related to a single roc file in the editor.
|
||||
#[derive(Debug)]
|
||||
pub struct EdModel<'a> {
|
||||
pub module: EdModule<'a>, // contains Abstract Syntax Tree of code
|
||||
pub file_path: &'a Path,
|
||||
pub code_lines: CodeLines, // Vec<String> of all code, this Vec is written to disk when saving a file.
|
||||
pub grid_node_map: GridNodeMap, // allows us to map window coordinates to MarkNodeId's
|
||||
pub markup_ids: Vec<MarkNodeId>, // one root node for every top level definition
|
||||
pub mark_node_pool: SlowPool, // all MarkupNodes for this file are saved into this pool and can be retrieved using their MarkNodeId
|
||||
pub mark_id_ast_id_map: MarkIdAstIdMap, // To find the ASTNode that is represented by a MarkNode
|
||||
pub glyph_dim_rect_opt: Option<Rect>, // represents the width and height of single monospace glyph(char)
|
||||
pub has_focus: bool,
|
||||
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>, // the editor supports multiple carets/cursors and multiple selections
|
||||
pub selected_block_opt: Option<SelectedBlock>, // a selected AST node, the roc type of this node is shown in the editor on ctrl+shift+"up arrow"
|
||||
pub loaded_module: LoadedModule, // contains all roc symbols, exposed values, exposed aliases, solved types... in the file(=module)
|
||||
pub show_debug_view: bool, // see render_debug.rs for the debug view
|
||||
pub dirty: bool, // EdModel is dirty if it has changed since the previous render.
|
||||
}
|
||||
|
||||
// a selected AST node, the roc type of this node is shown in the editor on ctrl+shift+"up arrow"
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SelectedBlock {
|
||||
pub ast_node_id: ASTNodeId,
|
||||
pub mark_node_id: MarkNodeId,
|
||||
pub type_str: PoolStr,
|
||||
}
|
||||
|
||||
pub fn init_model<'a>(
|
||||
code_str: &'a str, // entire roc file as one str
|
||||
file_path: &'a Path,
|
||||
env: Env<'a>, // contains all variables, identifiers, closures, top level symbols...
|
||||
loaded_module: LoadedModule, // contains all roc symbols, exposed values, exposed aliases, solved types... in the file(=module)
|
||||
code_arena: &'a Bump, // bump allocation arena, used for fast memory allocation
|
||||
caret_pos: CaretPos, // to set caret position when the file is displayed
|
||||
) -> EdResult<EdModel<'a>> {
|
||||
// for debugging
|
||||
//println!("{}", code_str);
|
||||
let mut owned_loaded_module = loaded_module;
|
||||
|
||||
let mut module = EdModule::new(code_str, env, &mut owned_loaded_module.interns, code_arena)?;
|
||||
|
||||
let mut mark_node_pool = SlowPool::default();
|
||||
|
||||
let (markup_ids, mark_id_ast_id_map) = if code_str.is_empty() {
|
||||
EmptyCodeStringSnafu {}.fail()
|
||||
} else {
|
||||
Ok(ast_to_mark_nodes(
|
||||
&mut module.env,
|
||||
&module.ast,
|
||||
&mut mark_node_pool,
|
||||
&owned_loaded_module.interns,
|
||||
)?)
|
||||
}?;
|
||||
|
||||
let code_lines =
|
||||
CodeLines::from_str(&nodes::mark_nodes_to_string(&markup_ids, &mark_node_pool));
|
||||
let mut grid_node_map = GridNodeMap::default();
|
||||
|
||||
let mut line_nr = 0;
|
||||
let mut col_nr = 0;
|
||||
|
||||
for mark_node_id in &markup_ids {
|
||||
// for debugging:
|
||||
//println!("{}", tree_as_string(*mark_node_id, &mark_node_pool));
|
||||
EdModel::insert_mark_node_between_line(
|
||||
&mut line_nr,
|
||||
&mut col_nr,
|
||||
*mark_node_id,
|
||||
&mut grid_node_map,
|
||||
&mark_node_pool,
|
||||
)?
|
||||
}
|
||||
|
||||
let caret = match caret_pos {
|
||||
CaretPos::Start => CaretWSelect::default(),
|
||||
CaretPos::Exact(txt_pos) => CaretWSelect::new(txt_pos, None),
|
||||
CaretPos::End => CaretWSelect::new(code_lines.end_txt_pos(), None),
|
||||
};
|
||||
|
||||
Ok(EdModel {
|
||||
module,
|
||||
file_path,
|
||||
code_lines,
|
||||
grid_node_map,
|
||||
markup_ids,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
glyph_dim_rect_opt: None,
|
||||
has_focus: true,
|
||||
caret_w_select_vec: NonEmpty::new((caret, None)),
|
||||
selected_block_opt: None,
|
||||
loaded_module: owned_loaded_module,
|
||||
show_debug_view: false,
|
||||
dirty: true,
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> EdModel<'a> {
|
||||
pub fn get_carets(&self) -> Vec<TextPos> {
|
||||
self.caret_w_select_vec
|
||||
.iter()
|
||||
.map(|tup| tup.0.caret_pos)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_curr_mark_node_id(&self) -> UIResult<MarkNodeId> {
|
||||
let caret_pos = self.get_caret();
|
||||
self.grid_node_map.get_id_at_row_col(caret_pos)
|
||||
}
|
||||
|
||||
// get id of MarkNode that is located before the caret
|
||||
pub fn get_prev_mark_node_id(&self) -> UIResult<Option<MarkNodeId>> {
|
||||
let caret_pos = self.get_caret();
|
||||
|
||||
let prev_id_opt = if caret_pos.column > 0 {
|
||||
let prev_mark_node_id = self.grid_node_map.get_id_at_row_col(TextPos {
|
||||
line: caret_pos.line,
|
||||
column: caret_pos.column - 1,
|
||||
})?;
|
||||
|
||||
Some(prev_mark_node_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(prev_id_opt)
|
||||
}
|
||||
|
||||
pub fn node_exists_at_caret(&self) -> bool {
|
||||
self.grid_node_map.node_exists_at_pos(self.get_caret())
|
||||
}
|
||||
|
||||
// return (index of child in list of children, closest ast index of child corresponding to ast node) of MarkupNode at current caret position
|
||||
pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> {
|
||||
if self.node_exists_at_caret() {
|
||||
let curr_mark_node_id = self.get_curr_mark_node_id()?;
|
||||
let curr_mark_node = self.mark_node_pool.get(curr_mark_node_id);
|
||||
|
||||
if let Some(parent_id) = curr_mark_node.get_parent_id_opt() {
|
||||
let parent = self.mark_node_pool.get(parent_id);
|
||||
let ast_node_id = self.mark_id_ast_id_map.get(curr_mark_node_id)?;
|
||||
Ok(parent.get_child_indices(
|
||||
curr_mark_node_id,
|
||||
ast_node_id,
|
||||
&self.mark_id_ast_id_map,
|
||||
)?)
|
||||
} else {
|
||||
MissingParentSnafu {
|
||||
node_id: curr_mark_node_id,
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
} else {
|
||||
NoNodeAtCaretPositionSnafu {
|
||||
caret_pos: self.get_caret(),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EdModule<'a> {
|
||||
pub env: Env<'a>,
|
||||
pub ast: AST,
|
||||
}
|
||||
|
||||
// for debugging
|
||||
//use crate::lang::ast::expr2_to_string;
|
||||
|
||||
impl<'a> EdModule<'a> {
|
||||
pub fn new(
|
||||
code_str: &'a str,
|
||||
mut env: Env<'a>,
|
||||
interns: &mut Interns, // contains ids of all identifiers in this roc file
|
||||
ast_arena: &'a Bump,
|
||||
) -> EdResult<EdModule<'a>> {
|
||||
if !code_str.is_empty() {
|
||||
let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena, interns);
|
||||
|
||||
match parse_res {
|
||||
Ok(ast) => Ok(EdModule { env, ast }),
|
||||
Err(err) => SrcParseSnafu {
|
||||
syntax_err: format!("{err:?}"),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
} else {
|
||||
EmptyCodeStringSnafu {}.fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_ed_model {
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::ed_model;
|
||||
use crate::editor::resources::strings::{
|
||||
nr_hello_world_lines, HELLO_WORLD, PLATFORM_DIR_NAME, PLATFORM_STR,
|
||||
};
|
||||
use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection;
|
||||
use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl;
|
||||
use crate::ui::text::caret_w_select::CaretPos;
|
||||
use crate::ui::text::lines::SelectableLines;
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
use crate::ui::ui_error::UIResult;
|
||||
use bumpalo::Bump;
|
||||
use ed_model::EdModel;
|
||||
use roc_ast::lang::env::Env;
|
||||
use roc_ast::mem_pool::pool::Pool;
|
||||
use roc_ast::module::load_module;
|
||||
use roc_load::{LoadedModule, Threading};
|
||||
use roc_module::symbol::IdentIds;
|
||||
use roc_module::symbol::ModuleIds;
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
use roc_types::subs::VarStore;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn init_dummy_model<'a>(
|
||||
code_str: &'a str,
|
||||
loaded_module: LoadedModule,
|
||||
module_ids: &'a ModuleIds,
|
||||
ed_model_refs: &'a mut EdModelRefs,
|
||||
code_arena: &'a Bump,
|
||||
) -> EdResult<EdModel<'a>> {
|
||||
let file_path = Path::new("");
|
||||
|
||||
let dep_idents = IdentIds::exposed_builtins(8);
|
||||
let exposed_ident_ids = IdentIds::default();
|
||||
|
||||
let env = Env::new(
|
||||
loaded_module.module_id,
|
||||
&ed_model_refs.env_arena,
|
||||
&mut ed_model_refs.env_pool,
|
||||
&mut ed_model_refs.var_store,
|
||||
dep_idents,
|
||||
module_ids,
|
||||
exposed_ident_ids,
|
||||
);
|
||||
|
||||
ed_model::init_model(
|
||||
code_str,
|
||||
file_path,
|
||||
env,
|
||||
loaded_module,
|
||||
code_arena,
|
||||
CaretPos::End,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct EdModelRefs {
|
||||
env_arena: Bump,
|
||||
env_pool: Pool,
|
||||
var_store: VarStore,
|
||||
}
|
||||
|
||||
pub fn init_model_refs() -> EdModelRefs {
|
||||
EdModelRefs {
|
||||
env_arena: Bump::new(),
|
||||
env_pool: Pool::with_capacity(1024),
|
||||
var_store: VarStore::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// We use a Domain Specific Language to clearly represent some of the editor's state.
|
||||
// Here we convert that DSL to an EdModel.
|
||||
// Example of dsl: "val = ┃❮5❯", 5 is selected and the caret is located before the 5.
|
||||
pub fn ed_model_from_dsl<'a>(
|
||||
clean_code_str: &'a mut String,
|
||||
code_lines: Vec<String>,
|
||||
ed_model_refs: &'a mut EdModelRefs,
|
||||
module_ids: &'a ModuleIds,
|
||||
code_arena: &'a Bump,
|
||||
) -> Result<EdModel<'a>, String> {
|
||||
// to be able to load the code as a LoadedModule we add a roc app header and a main function
|
||||
*clean_code_str = vec![HELLO_WORLD, clean_code_str.as_str()].join("");
|
||||
// for debugging
|
||||
//println!("{}", clean_code_str);
|
||||
|
||||
let temp_dir = tempdir().expect("Failed to create temporary directory for test.");
|
||||
|
||||
let platform_dir = temp_dir.path().join(PLATFORM_DIR_NAME);
|
||||
fs::create_dir(platform_dir.clone()).expect("Failed to create platform directory");
|
||||
let platform_module_path = platform_dir.join("main.roc");
|
||||
let mut platform_module_file =
|
||||
File::create(platform_module_path).expect("Failed to create main.roc");
|
||||
writeln!(platform_module_file, "{PLATFORM_STR}").expect("Failed to write to main.roc");
|
||||
|
||||
let temp_file_path_buf =
|
||||
PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join(""));
|
||||
let temp_file_full_path = temp_dir.path().join(temp_file_path_buf);
|
||||
|
||||
let mut file = File::create(temp_file_full_path.clone()).unwrap_or_else(|_| {
|
||||
panic!("Failed to create temporary file for path {temp_file_full_path:?}")
|
||||
});
|
||||
writeln!(file, "{clean_code_str}")
|
||||
.unwrap_or_else(|_| panic!("Failed to write {clean_code_str:?} to file: {file:?}"));
|
||||
|
||||
let loaded_module = load_module(
|
||||
&temp_file_full_path,
|
||||
RocCacheDir::Disallowed,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let mut ed_model = init_dummy_model(
|
||||
clean_code_str,
|
||||
loaded_module,
|
||||
module_ids,
|
||||
ed_model_refs,
|
||||
code_arena,
|
||||
)?;
|
||||
|
||||
// adjust caret for header and main function
|
||||
let caret_w_select = convert_dsl_to_selection(&code_lines)?;
|
||||
let adjusted_caret_pos = TextPos {
|
||||
line: caret_w_select.caret_pos.line + nr_hello_world_lines(),
|
||||
column: caret_w_select.caret_pos.column,
|
||||
};
|
||||
|
||||
ed_model.set_caret(adjusted_caret_pos);
|
||||
|
||||
Ok(ed_model)
|
||||
}
|
||||
|
||||
pub fn ed_model_to_dsl(ed_model: &EdModel) -> UIResult<Vec<String>> {
|
||||
let caret_w_select = ed_model.caret_w_select_vec.first().0;
|
||||
let code_lines = ed_model.code_lines.lines.clone();
|
||||
|
||||
convert_selection_to_dsl(caret_w_select, code_lines)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,199 +0,0 @@
|
||||
use super::ed_model::EdModel;
|
||||
use crate::editor::config::Config;
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::ed_model::SelectedBlock;
|
||||
use crate::editor::render_ast::build_code_graphics;
|
||||
use crate::editor::render_debug::build_debug_graphics;
|
||||
use crate::editor::resources::strings::START_TIP;
|
||||
use crate::graphics::primitives::rect::Rect;
|
||||
use crate::graphics::primitives::text::{owned_section_from_text, Text};
|
||||
use crate::ui::text::caret_w_select::make_caret_rect;
|
||||
use crate::ui::text::caret_w_select::make_selection_rect;
|
||||
use crate::ui::text::caret_w_select::CaretWSelect;
|
||||
use crate::ui::text::selection::Selection;
|
||||
use crate::ui::tooltip::ToolTip;
|
||||
use crate::ui::ui_error::MissingGlyphDimsSnafu;
|
||||
use cgmath::Vector2;
|
||||
use roc_ast::mem_pool::pool::Pool;
|
||||
use snafu::OptionExt;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderedWgpu {
|
||||
pub text_sections_behind: Vec<glyph_brush::OwnedSection>, // displayed in front of rect_behind, behind everything else
|
||||
pub text_sections_front: Vec<glyph_brush::OwnedSection>, // displayed in front of everything
|
||||
pub rects_behind: Vec<Rect>, // displayed at lowest depth
|
||||
pub rects_front: Vec<Rect>, // displayed in front of text_sections_behind, behind text_sections_front
|
||||
}
|
||||
|
||||
impl RenderedWgpu {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
text_sections_behind: Vec::new(),
|
||||
text_sections_front: Vec::new(),
|
||||
rects_behind: Vec::new(),
|
||||
rects_front: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) {
|
||||
self.text_sections_behind.push(new_text_section);
|
||||
}
|
||||
|
||||
pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) {
|
||||
self.text_sections_front.push(new_text_section);
|
||||
}
|
||||
|
||||
pub fn add_rect_behind(&mut self, new_rect: Rect) {
|
||||
self.rects_behind.push(new_rect);
|
||||
}
|
||||
|
||||
pub fn add_rects_behind(&mut self, new_rects: Vec<Rect>) {
|
||||
self.rects_behind.extend(new_rects);
|
||||
}
|
||||
|
||||
pub fn add_rect_front(&mut self, new_rect: Rect) {
|
||||
self.rects_front.push(new_rect);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) {
|
||||
self.text_sections_behind
|
||||
.extend(rendered_wgpu.text_sections_behind);
|
||||
self.text_sections_front
|
||||
.extend(rendered_wgpu.text_sections_front);
|
||||
self.rects_behind.extend(rendered_wgpu.rects_behind);
|
||||
self.rects_front.extend(rendered_wgpu.rects_front);
|
||||
}
|
||||
}
|
||||
|
||||
// create text and rectangles based on EdModel's markup_root
|
||||
pub fn model_to_wgpu(
|
||||
ed_model: &mut EdModel,
|
||||
size: &PhysicalSize<u32>,
|
||||
txt_coords: Vector2<f32>,
|
||||
config: &Config,
|
||||
) -> EdResult<RenderedWgpu> {
|
||||
let glyph_dim_rect = ed_model
|
||||
.glyph_dim_rect_opt
|
||||
.context(MissingGlyphDimsSnafu {})?;
|
||||
|
||||
let mut all_rendered = RenderedWgpu::new();
|
||||
|
||||
let tip_txt_coords = (
|
||||
txt_coords.x,
|
||||
txt_coords.y - (START_TIP.matches('\n').count() as f32 + 1.0) * config.code_font_size,
|
||||
);
|
||||
|
||||
let start_tip_text = owned_section_from_text(&Text {
|
||||
position: tip_txt_coords.into(),
|
||||
area_bounds: (size.width as f32, size.height as f32).into(),
|
||||
color: config.ed_theme.subtle_text,
|
||||
text: START_TIP,
|
||||
size: config.code_font_size,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
all_rendered.add_text_behind(start_tip_text);
|
||||
|
||||
let rendered_code_graphics = build_code_graphics(
|
||||
&ed_model.markup_ids,
|
||||
size,
|
||||
txt_coords,
|
||||
config,
|
||||
glyph_dim_rect,
|
||||
&ed_model.mark_node_pool,
|
||||
)?;
|
||||
|
||||
all_rendered.extend(rendered_code_graphics);
|
||||
|
||||
let caret_w_sel_vec = ed_model
|
||||
.caret_w_select_vec
|
||||
.iter()
|
||||
.map(|(caret_w_sel, _)| *caret_w_sel)
|
||||
.collect();
|
||||
|
||||
let rendered_selection = build_selection_graphics(
|
||||
caret_w_sel_vec,
|
||||
&ed_model.selected_block_opt,
|
||||
txt_coords,
|
||||
config,
|
||||
glyph_dim_rect,
|
||||
ed_model.module.env.pool,
|
||||
)?;
|
||||
|
||||
all_rendered.extend(rendered_selection);
|
||||
|
||||
if ed_model.show_debug_view {
|
||||
all_rendered.add_text_behind(build_debug_graphics(size, txt_coords, config, ed_model)?);
|
||||
}
|
||||
|
||||
Ok(all_rendered)
|
||||
}
|
||||
|
||||
pub fn build_selection_graphics(
|
||||
caret_w_select_vec: Vec<CaretWSelect>,
|
||||
selected_expr_opt: &Option<SelectedBlock>,
|
||||
txt_coords: Vector2<f32>,
|
||||
config: &Config,
|
||||
glyph_dim_rect: Rect,
|
||||
pool: &Pool,
|
||||
) -> EdResult<RenderedWgpu> {
|
||||
let mut all_rendered = RenderedWgpu::new();
|
||||
let char_width = glyph_dim_rect.width;
|
||||
let char_height = glyph_dim_rect.height;
|
||||
|
||||
let y_offset = 0.1 * char_height;
|
||||
|
||||
for caret_w_sel in caret_w_select_vec {
|
||||
let caret_row = caret_w_sel.caret_pos.line as f32;
|
||||
let caret_col = caret_w_sel.caret_pos.column as f32;
|
||||
|
||||
let top_left_x = txt_coords.x + caret_col * char_width;
|
||||
let top_left_y = txt_coords.y + caret_row * char_height + y_offset;
|
||||
|
||||
if let Some(selection) = caret_w_sel.selection_opt {
|
||||
let Selection { start_pos, end_pos } = selection;
|
||||
|
||||
let sel_rect_x = txt_coords.x + ((start_pos.column as f32) * char_width);
|
||||
let sel_rect_y = txt_coords.y + char_height * (start_pos.line as f32) + y_offset;
|
||||
|
||||
let width =
|
||||
((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width);
|
||||
|
||||
all_rendered.add_rect_behind(make_selection_rect(
|
||||
sel_rect_x,
|
||||
sel_rect_y,
|
||||
width,
|
||||
&glyph_dim_rect,
|
||||
&config.ed_theme.ui_theme,
|
||||
));
|
||||
|
||||
// render tooltip showing type
|
||||
if let Some(selected_expr) = selected_expr_opt {
|
||||
let tooltip = ToolTip {
|
||||
position_x: sel_rect_x,
|
||||
position_y: sel_rect_y - glyph_dim_rect.height,
|
||||
text: selected_expr.type_str.as_str(pool),
|
||||
};
|
||||
|
||||
let (tip_rect, tip_text) = tooltip.render_tooltip(
|
||||
&glyph_dim_rect,
|
||||
&config.ed_theme.ui_theme,
|
||||
config.code_font_size,
|
||||
);
|
||||
|
||||
all_rendered.add_rect_front(tip_rect);
|
||||
all_rendered.add_text_front(tip_text);
|
||||
}
|
||||
}
|
||||
|
||||
all_rendered.add_rect_front(make_caret_rect(
|
||||
top_left_x,
|
||||
top_left_y,
|
||||
&glyph_dim_rect,
|
||||
&config.ed_theme.ui_theme,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(all_rendered)
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
use roc_ast::lang::core::expr::expr2::Expr2::SmallInt;
|
||||
use roc_ast::lang::core::expr::expr2::IntStyle;
|
||||
use roc_ast::lang::core::expr::expr2::IntVal;
|
||||
use roc_ast::mem_pool::pool_str::PoolStr;
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::ed_error::StringParseSnafu;
|
||||
use crate::editor::mvc::app_update::InputOutcome;
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::editor::mvc::ed_update::get_node_context;
|
||||
use crate::editor::mvc::ed_update::NodeContext;
|
||||
use crate::ui::text::lines::SelectableLines;
|
||||
|
||||
// digit_char should be verified to be a digit before calling this function
|
||||
pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
let is_blank_node = curr_mark_node.is_blank();
|
||||
|
||||
let int_var = ed_model.module.env.var_store.fresh();
|
||||
|
||||
let digit_string = digit_char.to_string();
|
||||
|
||||
let expr2_node = SmallInt {
|
||||
number: IntVal::U64(*digit_char as u64), // TODO determine if u64 on wordlength of current arch, perhaps introduce Unknown(i64)
|
||||
var: int_var,
|
||||
style: IntStyle::Decimal,
|
||||
text: PoolStr::new(&digit_string, ed_model.module.env.pool),
|
||||
};
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(ast_node_id.to_expr_id()?, expr2_node);
|
||||
|
||||
if is_blank_node {
|
||||
let char_len = 1;
|
||||
ed_model.simple_move_carets_right(char_len);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check if new int needs more than e.g. 64 bits
|
||||
pub fn update_int(
|
||||
ed_model: &mut EdModel,
|
||||
int_mark_node_id: MarkNodeId,
|
||||
ch: &char,
|
||||
) -> EdResult<InputOutcome> {
|
||||
if ch.is_ascii_digit() {
|
||||
let old_caret_pos = ed_model.get_caret();
|
||||
|
||||
let node_caret_offset = ed_model
|
||||
.grid_node_map
|
||||
.get_offset_to_node_id(old_caret_pos, int_mark_node_id)?;
|
||||
|
||||
let int_mark_node = ed_model.mark_node_pool.get_mut(int_mark_node_id);
|
||||
let int_ast_node_id = ed_model.mark_id_ast_id_map.get(int_mark_node_id)?;
|
||||
|
||||
let content_str_mut = int_mark_node.get_content_mut()?;
|
||||
|
||||
// 00, 01 are not valid ints
|
||||
if (content_str_mut == "0" && (node_caret_offset == 1 || *ch == '0'))
|
||||
|| (*ch == '0' && node_caret_offset == 0)
|
||||
{
|
||||
Ok(InputOutcome::Ignored)
|
||||
} else {
|
||||
content_str_mut.insert(node_caret_offset, *ch);
|
||||
|
||||
let content_str = int_mark_node.get_content();
|
||||
|
||||
// update ast
|
||||
let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool);
|
||||
let int_ast_node = ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.get_mut(int_ast_node_id.to_expr_id()?);
|
||||
match int_ast_node {
|
||||
SmallInt { number, text, .. } => {
|
||||
update_small_int_num(number, &content_str)?;
|
||||
|
||||
*text = new_pool_str;
|
||||
}
|
||||
_ => unimplemented!("TODO implement updating this type of Number"),
|
||||
}
|
||||
|
||||
// update caret
|
||||
ed_model.simple_move_carets_right(1);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
}
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> {
|
||||
use IntVal::*;
|
||||
|
||||
*number = match number {
|
||||
I64(_) => I64(check_parse_res(updated_str.parse::<i64>())?),
|
||||
U64(_) => U64(check_parse_res(updated_str.parse::<u64>())?),
|
||||
I32(_) => I32(check_parse_res(updated_str.parse::<i32>())?),
|
||||
U32(_) => U32(check_parse_res(updated_str.parse::<u32>())?),
|
||||
I16(_) => I16(check_parse_res(updated_str.parse::<i16>())?),
|
||||
U16(_) => U16(check_parse_res(updated_str.parse::<u16>())?),
|
||||
I8(_) => I8(check_parse_res(updated_str.parse::<i8>())?),
|
||||
U8(_) => U8(check_parse_res(updated_str.parse::<u8>())?),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_parse_res<T, E: std::fmt::Debug>(parse_res: Result<T, E>) -> EdResult<T> {
|
||||
match parse_res {
|
||||
Ok(some_type) => Ok(some_type),
|
||||
Err(parse_err) => StringParseSnafu {
|
||||
msg: format!("{parse_err:?}"),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
use roc_ast::lang::core::expr::expr2::Expr2;
|
||||
use roc_ast::lang::core::pattern::Pattern2;
|
||||
use roc_ast::lang::core::val_def::ValueDef;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::app_update::InputOutcome;
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::editor::mvc::ed_update::get_node_context;
|
||||
use crate::editor::mvc::ed_update::NodeContext;
|
||||
|
||||
pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
let is_blank_node = curr_mark_node.is_blank();
|
||||
|
||||
let val_name_string = new_char.to_string();
|
||||
// safe unwrap because our ArrString has a 30B capacity
|
||||
let val_expr2_node = Expr2::Blank;
|
||||
let val_expr_id = ed_model.module.env.pool.add(val_expr2_node);
|
||||
|
||||
let ident_id = ed_model.module.env.ident_ids.add_str(&val_name_string);
|
||||
let var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
|
||||
let body = Expr2::Var(var_symbol);
|
||||
let body_id = ed_model.module.env.pool.add(body);
|
||||
|
||||
let pattern = Pattern2::Identifier(var_symbol);
|
||||
let pattern_id = ed_model.module.env.pool.add(pattern);
|
||||
|
||||
let value_def = ValueDef::NoAnnotation {
|
||||
pattern_id,
|
||||
expr_id: val_expr_id,
|
||||
expr_var: ed_model.module.env.var_store.fresh(),
|
||||
};
|
||||
let def_id = ed_model.module.env.pool.add(value_def);
|
||||
|
||||
let expr2_node = Expr2::LetValue {
|
||||
def_id,
|
||||
body_id,
|
||||
body_var: ed_model.module.env.var_store.fresh(),
|
||||
};
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(ast_node_id.to_expr_id()?, expr2_node);
|
||||
|
||||
if is_blank_node {
|
||||
let char_len = 1;
|
||||
ed_model.simple_move_carets_right(char_len);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO reenable this for updating non-top level value defs
|
||||
/*
|
||||
pub fn update_let_value(
|
||||
val_name_mn_id: MarkNodeId,
|
||||
def_id: NodeId<ValueDef>,
|
||||
body_id: NodeId<Expr2>,
|
||||
ed_model: &mut EdModel,
|
||||
new_char: &char,
|
||||
) -> EdResult<InputOutcome> {
|
||||
if new_char.is_ascii_alphanumeric() {
|
||||
let old_caret_pos = ed_model.get_caret();
|
||||
|
||||
// update markup
|
||||
let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id);
|
||||
let content_str_mut = val_name_mn_mut.get_content_mut()?;
|
||||
|
||||
let old_val_name = content_str_mut.clone();
|
||||
|
||||
let node_caret_offset = ed_model
|
||||
.grid_node_map
|
||||
.get_offset_to_node_id(old_caret_pos, val_name_mn_id)?;
|
||||
|
||||
if node_caret_offset <= content_str_mut.len() {
|
||||
content_str_mut.insert(node_caret_offset, *new_char);
|
||||
|
||||
// update ast
|
||||
let value_def = ed_model.module.env.pool.get(def_id);
|
||||
let value_ident_pattern_id = value_def.get_pattern_id();
|
||||
|
||||
// TODO no unwrap
|
||||
let ident_id = ed_model
|
||||
.module
|
||||
.env
|
||||
.ident_ids
|
||||
.update_key(&old_val_name, content_str_mut)
|
||||
.unwrap();
|
||||
|
||||
let new_var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(value_ident_pattern_id, Pattern2::Identifier(new_var_symbol));
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(body_id, Expr2::Var(new_var_symbol));
|
||||
|
||||
// update GridNodeMap and CodeLines
|
||||
ed_model.insert_between_line(
|
||||
old_caret_pos.line,
|
||||
old_caret_pos.column,
|
||||
&new_char.to_string(),
|
||||
val_name_mn_id,
|
||||
)?;
|
||||
|
||||
// update caret
|
||||
ed_model.simple_move_carets_right(1);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,123 +0,0 @@
|
||||
use roc_ast::lang::core::ast::ast_node_to_string;
|
||||
use roc_ast::lang::core::expr::expr2::{Expr2, ExprId};
|
||||
use roc_ast::mem_pool::pool_vec::PoolVec;
|
||||
use roc_code_markup::markup::nodes::{self};
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::ed_error::{MissingParentSnafu, UnexpectedASTNodeSnafu};
|
||||
use crate::editor::mvc::app_update::InputOutcome;
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::editor::mvc::ed_update::get_node_context;
|
||||
use crate::editor::mvc::ed_update::NodeContext;
|
||||
|
||||
pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
let is_blank_node = curr_mark_node.is_blank();
|
||||
|
||||
let expr2_node = Expr2::List {
|
||||
elem_var: ed_model.module.env.var_store.fresh(),
|
||||
elems: PoolVec::empty(ed_model.module.env.pool),
|
||||
};
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(ast_node_id.to_expr_id()?, expr2_node);
|
||||
|
||||
if is_blank_node {
|
||||
ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len());
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
// insert Blank at current position for easy code reuse
|
||||
pub fn add_blank_child(
|
||||
new_child_index: usize,
|
||||
new_ast_child_index: usize,
|
||||
ed_model: &mut EdModel,
|
||||
) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id,
|
||||
curr_mark_node: _,
|
||||
parent_id_opt,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt
|
||||
{
|
||||
let list_ast_node_id = ed_model.mark_id_ast_id_map.get(parent_id)?;
|
||||
let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id.to_expr_id()?);
|
||||
|
||||
match list_ast_node {
|
||||
Expr2::List {
|
||||
elem_var: _,
|
||||
elems: _,
|
||||
} => {
|
||||
let blank_elt = Expr2::Blank;
|
||||
let blank_elt_id = ed_model.module.env.pool.add(blank_elt);
|
||||
|
||||
Ok((blank_elt_id, list_ast_node_id.to_expr_id()?, parent_id))
|
||||
}
|
||||
_ => UnexpectedASTNodeSnafu {
|
||||
required_node_type: "List".to_string(),
|
||||
encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
} else {
|
||||
MissingParentSnafu {
|
||||
node_id: curr_mark_node_id,
|
||||
}
|
||||
.fail()
|
||||
};
|
||||
|
||||
let (blank_elt_id, list_ast_node_id, _parent_id) = trip_result?;
|
||||
|
||||
let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id);
|
||||
|
||||
match list_ast_node {
|
||||
Expr2::List { elem_var, elems } => {
|
||||
let mut new_elems: Vec<ExprId> =
|
||||
elems.iter(ed_model.module.env.pool).copied().collect();
|
||||
|
||||
new_elems.insert(new_ast_child_index, blank_elt_id);
|
||||
|
||||
let new_list_node = Expr2::List {
|
||||
elem_var: *elem_var,
|
||||
elems: PoolVec::new(new_elems.into_iter(), ed_model.module.env.pool),
|
||||
};
|
||||
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(list_ast_node_id, new_list_node);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => UnexpectedASTNodeSnafu {
|
||||
required_node_type: "List".to_string(),
|
||||
encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool),
|
||||
}
|
||||
.fail(),
|
||||
}?;
|
||||
|
||||
if new_child_index > 1 {
|
||||
ed_model.simple_move_carets_right(nodes::COMMA.len());
|
||||
}
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
use roc_ast::lang::core::expr::expr2::{Expr2, ExprId};
|
||||
use roc_ast::mem_pool::pool_str::PoolStr;
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::app_update::InputOutcome;
|
||||
use crate::editor::mvc::ed_model::EdModel;
|
||||
use crate::ui::text::lines::SelectableLines;
|
||||
|
||||
pub fn update_invalid_lookup(
|
||||
input_str: &str,
|
||||
old_pool_str: &PoolStr,
|
||||
curr_mark_node_id: MarkNodeId,
|
||||
expr_id: ExprId,
|
||||
ed_model: &mut EdModel,
|
||||
) -> EdResult<InputOutcome> {
|
||||
if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) {
|
||||
let mut new_lookup_str = String::new();
|
||||
|
||||
new_lookup_str.push_str(old_pool_str.as_str(ed_model.module.env.pool));
|
||||
|
||||
let caret_offset = ed_model
|
||||
.grid_node_map
|
||||
.get_offset_to_node_id(ed_model.get_caret(), curr_mark_node_id)?;
|
||||
|
||||
new_lookup_str.insert_str(caret_offset, input_str);
|
||||
|
||||
let new_pool_str = PoolStr::new(&new_lookup_str, ed_model.module.env.pool);
|
||||
|
||||
// update AST
|
||||
ed_model
|
||||
.module
|
||||
.env
|
||||
.pool
|
||||
.set(expr_id, Expr2::InvalidLookup(new_pool_str));
|
||||
|
||||
// update caret
|
||||
ed_model.simple_move_carets_right(input_str.len());
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user