mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
Merge pull request #4813 from joshuawarner32/test_syntax
Move syntax tests to a dedicated crate
This commit is contained in:
commit
c3b0295a92
3
.github/workflows/ubuntu_x86_64.yml
vendored
3
.github/workflows/ubuntu_x86_64.yml
vendored
@ -57,6 +57,9 @@ jobs:
|
||||
- name: wasm repl test
|
||||
run: crates/repl_test/test_wasm.sh && sccache --show-stats
|
||||
|
||||
- name: test building wasm repl
|
||||
run: ./ci/www-repl.sh && sccache --show-stats
|
||||
|
||||
#TODO i386 (32-bit linux) cli tests
|
||||
|
||||
#TODO verify-no-git-changes
|
||||
|
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -3602,14 +3602,10 @@ name = "roc_fmt"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_test_utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4919,6 +4915,22 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_syntax"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_collections",
|
||||
"roc_fmt",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_test_utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
@ -11,10 +11,4 @@ roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
bumpalo.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
walkdir.workspace = true
|
||||
|
@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "roc_fmt-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
roc_parse = { path = "../../parse" }
|
||||
|
||||
[dependencies.roc_fmt]
|
||||
path = ".."
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_expr"
|
||||
path = "fuzz_targets/fuzz_expr.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_defs"
|
||||
path = "fuzz_targets/fuzz_defs.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_header"
|
||||
path = "fuzz_targets/fuzz_header.rs"
|
||||
test = false
|
||||
doc = false
|
@ -1,16 +0,0 @@
|
||||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
use roc_fmt::annotation::{Formattable, Parens, Newlines};
|
||||
use roc_fmt::Buf;
|
||||
use roc_fmt::test_helpers::expr_formats;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
expr_formats(input, |_| (), false);
|
||||
}
|
||||
}
|
||||
});
|
@ -288,9 +288,18 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
buf.push(')')
|
||||
}
|
||||
}
|
||||
BoundVariable(v) => buf.push_str(v),
|
||||
Wildcard => buf.push('*'),
|
||||
Inferred => buf.push('_'),
|
||||
BoundVariable(v) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(v)
|
||||
}
|
||||
Wildcard => {
|
||||
buf.indent(indent);
|
||||
buf.push('*')
|
||||
}
|
||||
Inferred => {
|
||||
buf.indent(indent);
|
||||
buf.push('_')
|
||||
}
|
||||
|
||||
TagUnion { tags, ext } => {
|
||||
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
|
||||
@ -355,8 +364,10 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
ann.format_with_options(buf, parens, newlines, indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
|
||||
Malformed(raw) => buf.push_str(raw),
|
||||
Malformed(raw) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ pub mod expr;
|
||||
pub mod module;
|
||||
pub mod pattern;
|
||||
pub mod spaces;
|
||||
pub mod test_helpers;
|
||||
|
||||
use bumpalo::{collections::String, Bump};
|
||||
use roc_parse::ast::Module;
|
||||
|
@ -1,77 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
use crate::{
|
||||
annotation::{Formattable, Newlines, Parens},
|
||||
Buf,
|
||||
};
|
||||
|
||||
/// Parse and re-format the given input, and pass the output to `check_formatting`
|
||||
/// for verification. The expectation is that `check_formatting` assert the result matches
|
||||
/// expectations (or, overwrite the expectation based on a command-line flag)
|
||||
/// Optionally, based on the value of `check_idempotency`, also verify that the formatting
|
||||
/// is idempotent - that if we reformat the output, we get the same result.
|
||||
pub fn expr_formats(input: &str, check_formatting: impl Fn(&str), check_idempotency: bool) {
|
||||
let arena = Bump::new();
|
||||
let input = input.trim();
|
||||
|
||||
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
|
||||
Ok(actual) => {
|
||||
use crate::spaces::RemoveSpaces;
|
||||
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
|
||||
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
let output = buf.as_str();
|
||||
|
||||
check_formatting(output);
|
||||
|
||||
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\n\
|
||||
Parse error was: {:?}\n\n\
|
||||
The code that failed to parse:\n\n{}\n\n\
|
||||
The original ast was:\n\n{:#?}\n\n",
|
||||
err, output, actual
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n\
|
||||
* * * AST before formatting:\n{:#?}\n\n\
|
||||
* * * AST after formatting:\n{:#?}\n\n",
|
||||
input,
|
||||
output,
|
||||
ast_normalized,
|
||||
reparsed_ast_normalized
|
||||
);
|
||||
}
|
||||
|
||||
// Now verify that the resultant formatting is _idempotent_ - i.e. that it doesn't change again if re-formatted
|
||||
if check_idempotency {
|
||||
let mut reformatted_buf = Buf::new_in(&arena);
|
||||
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
if output != reformatted_buf.as_str() {
|
||||
eprintln!("Formatting bug; formatting is not stable.\nOriginal code:\n{}\n\nFormatted code:\n{}\n\n", input, output);
|
||||
eprintln!("Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output, reformatted_buf.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
||||
};
|
||||
}
|
3
crates/compiler/parse/fuzz/.gitignore
vendored
3
crates/compiler/parse/fuzz/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
424
crates/compiler/parse/fuzz/Cargo.lock
generated
424
crates/compiler/parse/fuzz/Cargo.lock
generated
@ -1,424 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bumpalo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "im"
|
||||
version = "15.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "im-rc"
|
||||
version = "15.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.131"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_collections"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"bumpalo",
|
||||
"fnv",
|
||||
"hashbrown",
|
||||
"im",
|
||||
"im-rc",
|
||||
"wyhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_error_macros"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_ident"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_module"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_ident",
|
||||
"roc_region",
|
||||
"snafu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_parse"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"encode_unicode",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_parse-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"libfuzzer-sys",
|
||||
"roc_parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_region"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wyhash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
@ -1,13 +0,0 @@
|
||||
# fuzz
|
||||
|
||||
To setup fuzzing you will need to install cargo-fuzz and run with rust nightly:
|
||||
|
||||
```sh
|
||||
$ cargo install cargo-fuzz
|
||||
$ cargo +nightly fuzz run -j<cores> <target> -- -dict=dict.txt
|
||||
```
|
||||
|
||||
The different targets can be found by running `cargo fuzz list`.
|
||||
|
||||
When a bug is found, it will be reported with commands to run it again and look for a minimized version.
|
||||
If you are going to file a bug, please minimize the input before filing the bug.
|
@ -1,11 +0,0 @@
|
||||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_header_with;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_header_with(&arena, input.trim());
|
||||
}
|
||||
});
|
384
crates/compiler/parse/generator.roc
Normal file
384
crates/compiler/parse/generator.roc
Normal file
@ -0,0 +1,384 @@
|
||||
app "generator"
|
||||
packages {
|
||||
pf: "https://github.com/roc-lang/basic-cli/releases/download/0.1.3/5SXwdW7rH8QAOnD71IkHcFxCmBEPtFSLAIkclPEgjHQ.tar.br",
|
||||
}
|
||||
imports [
|
||||
pf.Stdout,
|
||||
pf.File,
|
||||
pf.Path,
|
||||
pf.Task.{ Task },
|
||||
]
|
||||
|
||||
provides [main] to pf
|
||||
|
||||
RenderTree : [
|
||||
Text Str,
|
||||
Items (List RenderTree),
|
||||
Line (List RenderTree),
|
||||
Indent (List RenderTree),
|
||||
Import {modu: List Str, name: Str},
|
||||
]
|
||||
|
||||
renderFile : RenderTree -> Str
|
||||
renderFile = \tree ->
|
||||
render (Items [
|
||||
formatImports (findImports tree),
|
||||
tree
|
||||
])
|
||||
|
||||
findImports : RenderTree -> Set { modu: (List Str), name: Str }
|
||||
findImports = \tree ->
|
||||
when tree is
|
||||
Text _ -> Set.empty
|
||||
Items list | Indent list | Line list ->
|
||||
List.walk list Set.empty \acc, item -> Set.union acc (findImports item)
|
||||
Import import -> Set.single import
|
||||
|
||||
formatImports : Set { modu: (List Str), name: Str } -> RenderTree
|
||||
formatImports = \set ->
|
||||
if hasDupImports set then
|
||||
crash "Duplicate imports!"
|
||||
else
|
||||
Items (
|
||||
set
|
||||
|> Set.toList
|
||||
# TODO: Sort by module name
|
||||
|> List.map \{ modu, name } ->
|
||||
Line [
|
||||
Text "use ",
|
||||
Text (Str.joinWith (List.map modu \m -> Str.concat m "::") ""),
|
||||
Text name,
|
||||
Text ";",
|
||||
]
|
||||
)
|
||||
|
||||
hasDupImports : Set { modu: (List Str), name: Str } -> Bool
|
||||
hasDupImports = \set ->
|
||||
nameSet =
|
||||
set
|
||||
|> Set.toList
|
||||
|> List.map \{ modu: _, name } -> name
|
||||
|> Set.fromList
|
||||
|
||||
Set.len nameSet != Set.len nameSet
|
||||
|
||||
render : RenderTree -> Str
|
||||
render = \tree ->
|
||||
Tuple text _ = renderInner tree 0 Bool.true
|
||||
text
|
||||
|
||||
renderGroup : List RenderTree, Nat, Bool -> [Tuple Str Bool]
|
||||
renderGroup = \list, indent, newlineBefore ->
|
||||
List.walk list (Tuple "" newlineBefore) \(Tuple text nlb), item ->
|
||||
Tuple ntext nla = renderInner item indent nlb
|
||||
(Tuple
|
||||
(Str.concat text ntext)
|
||||
nla
|
||||
)
|
||||
|
||||
|
||||
renderInner : RenderTree, Nat, Bool -> [Tuple Str Bool]
|
||||
renderInner = \tree, indent, newlineBefore ->
|
||||
when tree is
|
||||
Text text ->
|
||||
result = if newlineBefore then
|
||||
Str.concat (Str.repeat " " (4*indent)) text
|
||||
else
|
||||
text
|
||||
Tuple result Bool.false
|
||||
Items list -> renderGroup list indent newlineBefore
|
||||
Line list ->
|
||||
Tuple ntext nla = renderGroup list indent Bool.true
|
||||
res = if newlineBefore then
|
||||
# Already added the newline, no need!
|
||||
ntext
|
||||
else
|
||||
Str.concat "\n" ntext
|
||||
res2 = if nla then
|
||||
res
|
||||
else
|
||||
Str.concat res "\n"
|
||||
(Tuple res2 Bool.true)
|
||||
Indent list -> renderGroup list (indent + 1) newlineBefore
|
||||
Import {modu: _, name} ->
|
||||
Tuple name Bool.false
|
||||
|
||||
parserTrait = \t, e ->
|
||||
genCall (Import {modu: ["crate", "parser"], name: "Parser"}) [Text "'a", t, e]
|
||||
|
||||
parseFunction : Str, RenderTree, RenderTree, RenderTree -> RenderTree
|
||||
parseFunction = \name, ty, err, body ->
|
||||
Items [
|
||||
Line [
|
||||
Text "pub fn \(name)<'a>() -> impl ",
|
||||
parserTrait ty err,
|
||||
Text " {",
|
||||
],
|
||||
Indent [body, Line [Text ".trace(\"\(name)\")" ] ],
|
||||
Line [Text "}"],
|
||||
Line [Text ""],
|
||||
]
|
||||
|
||||
Type : RenderTree
|
||||
ErrTy : RenderTree
|
||||
|
||||
Parser : [
|
||||
Loc Parser,
|
||||
Specialize ErrTy Parser,
|
||||
Record Str (List { name: Str, parser: Parser }),
|
||||
Builtin RenderTree Type,
|
||||
# Named ParserName (World -> Parser),
|
||||
]
|
||||
|
||||
errHeader : Str -> ErrTy
|
||||
errHeader = \name ->
|
||||
Items [
|
||||
Import { modu: ["crate", "parser"], name: "EHeader" },
|
||||
Text "::",
|
||||
Text name,
|
||||
]
|
||||
|
||||
fnCall : RenderTree, List RenderTree -> RenderTree
|
||||
fnCall = \fnName, args ->
|
||||
Items [
|
||||
fnName,
|
||||
Text "(",
|
||||
Items (List.intersperse args (Text ",")),
|
||||
Text ")",
|
||||
]
|
||||
|
||||
fn : RenderTree -> (List RenderTree -> RenderTree)
|
||||
fn = \fnName -> \args -> fnCall fnName args
|
||||
|
||||
genCall : RenderTree, List RenderTree -> RenderTree
|
||||
genCall = \genName, args ->
|
||||
Items [
|
||||
genName,
|
||||
Text "<",
|
||||
Items (List.intersperse args (Text ", ")),
|
||||
Text ">",
|
||||
]
|
||||
|
||||
gen : RenderTree -> (List RenderTree -> RenderTree)
|
||||
gen = \genName -> \args -> genCall genName args
|
||||
|
||||
ref : RenderTree -> RenderTree
|
||||
ref = \name -> Items [Text "&'a ", name]
|
||||
|
||||
slice : RenderTree -> RenderTree
|
||||
slice = \name -> Items [Text "[", name, Text "]"]
|
||||
|
||||
refSlice : RenderTree -> RenderTree
|
||||
refSlice = \name -> ref (slice name)
|
||||
|
||||
commentOrNewline = genCall (Import {modu: ["crate", "ast"], name: "CommentOrNewline"}) [ Text "'a" ]
|
||||
|
||||
exposedName = genCall (Import {modu: ["crate", "header"], name: "ExposedName"}) [ Text "'a" ]
|
||||
importsEntry = genCall (Import {modu: ["crate", "header"], name: "ImportsEntry"}) [ Text "'a" ]
|
||||
uppercaseIdent = genCall (Import {modu: ["crate", "ident"], name: "UppercaseIdent"}) [ Text "'a" ]
|
||||
|
||||
moduleName = genCall (Import {modu: ["crate", "header"], name: "ModuleName"}) [ Text "'a" ]
|
||||
|
||||
space0E = fn (Import { modu: ["crate", "blankspace"], name: "space0_e" })
|
||||
|
||||
keyword = \keywordName ->
|
||||
Import { modu: ["crate", "header"], name: keywordName }
|
||||
|
||||
spaces = \errorName ->
|
||||
Builtin (space0E [errorName]) (refSlice commentOrNewline)
|
||||
|
||||
loc = gen (Import {modu: ["roc_region", "all"], name: "Loc"})
|
||||
|
||||
keywordItem = \kw, ty ->
|
||||
genCall (Import {modu: ["crate", "header"], name: "KeywordItem"}) [ Text "'a", kw, ty ]
|
||||
|
||||
collection = \ty ->
|
||||
genCall (Import {modu: ["crate", "ast"], name: "Collection"}) [ Text "'a", ty ]
|
||||
|
||||
spaced = \ty ->
|
||||
genCall (Import {modu: ["crate", "ast"], name: "Spaced"}) [ Text "'a", ty ]
|
||||
|
||||
moduleNameHelp = \err ->
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "module_name_help" }) [ err ]) moduleName
|
||||
|
||||
exposesValues =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "exposes_values"}) []) (keywordItem (keyword "ExposesKeyword") (collection (loc [spaced exposedName])))
|
||||
|
||||
imports =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "imports"}) []) (keywordItem (keyword "ImportsKeyword") (collection (loc [spaced importsEntry])))
|
||||
|
||||
generates =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "generates"}) []) (keywordItem (keyword "GeneratesKeyword") uppercaseIdent)
|
||||
|
||||
generatesWith =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "generates_with"}) []) (keywordItem (keyword "WithKeyword") (collection (loc [spaced exposedName])))
|
||||
|
||||
|
||||
interfaceHeader = Record "InterfaceHeader" [
|
||||
{name: "before_name", parser: spaces (errHeader "IndentStart")},
|
||||
{name: "name", parser: Loc (moduleNameHelp (errHeader "ModuleName"))},
|
||||
{name: "exposes", parser: Specialize (errHeader "Exposes") exposesValues },
|
||||
{name: "imports", parser: Specialize (errHeader "Imports") imports },
|
||||
]
|
||||
|
||||
hostedHeader = Record "HostedHeader" [
|
||||
{name: "before_name", parser: spaces (errHeader "IndentStart")},
|
||||
{name: "name", parser: Loc (moduleNameHelp (errHeader "ModuleName"))},
|
||||
{name: "exposes", parser: Specialize (errHeader "Exposes") exposesValues},
|
||||
{name: "imports", parser: Specialize (errHeader "Imports") imports},
|
||||
{name: "generates", parser: Specialize (errHeader "Generates") generates},
|
||||
{name: "generates_with", parser: Specialize (errHeader "GeneratesWith") generatesWith},
|
||||
]
|
||||
|
||||
printCombinatorParserFunction = \parser ->
|
||||
parseFunction (lowerName (resolveName parser)) (resolveType parser) (Text "EHeader<'a>") (printCombinatorParser parser)
|
||||
|
||||
resolveName : Parser -> Str
|
||||
resolveName = \parser ->
|
||||
when parser is
|
||||
Loc _p -> crash "Unnamed parser!"
|
||||
Specialize _err _p -> crash "Unnamed parser!"
|
||||
Builtin _name _ty -> crash "Unnamed parser!"
|
||||
Record name _fields -> name
|
||||
|
||||
underscoreScalar = 95
|
||||
aLowerScalar = 97
|
||||
aUpperScalar = 65
|
||||
zUpperScalar = 90
|
||||
|
||||
# map from a lower_case_name to a UpperCaseName
|
||||
upperName : Str -> Str
|
||||
upperName = \name ->
|
||||
result = Str.walkScalars name {text: "", needUpper: Bool.true} \{text, needUpper}, c ->
|
||||
if c == underscoreScalar then
|
||||
{text, needUpper: Bool.true}
|
||||
else
|
||||
newText =
|
||||
if needUpper then
|
||||
Str.appendScalar text (c - aLowerScalar + aUpperScalar) |> orCrash
|
||||
else
|
||||
Str.appendScalar text c |> orCrash
|
||||
{text: newText, needUpper: Bool.false}
|
||||
result.text
|
||||
|
||||
expect (upperName "hello_world") == "HelloWorld"
|
||||
|
||||
orCrash : Result a e -> a
|
||||
orCrash = \result ->
|
||||
when result is
|
||||
Ok a -> a
|
||||
Err _e -> crash "orCrash"
|
||||
|
||||
lowerName : Str -> Str
|
||||
lowerName = \name ->
|
||||
result = Str.walkScalars name {text: "", needUnder: Bool.false} \{text, needUnder}, c ->
|
||||
newText =
|
||||
if c >= aUpperScalar && c <= zUpperScalar then
|
||||
if needUnder then
|
||||
text
|
||||
|> Str.appendScalar underscoreScalar
|
||||
|> orCrash
|
||||
|> Str.appendScalar (c - aUpperScalar + aLowerScalar)
|
||||
|> orCrash
|
||||
else
|
||||
text
|
||||
|> Str.appendScalar (c - aUpperScalar + aLowerScalar)
|
||||
|> orCrash
|
||||
else
|
||||
Str.appendScalar text c |> orCrash
|
||||
|
||||
{text: newText, needUnder: Bool.true}
|
||||
|
||||
result.text
|
||||
|
||||
expect
|
||||
theResult = (lowerName "HelloWorld")
|
||||
theResult == "hello_world"
|
||||
|
||||
resolveType : Parser -> RenderTree
|
||||
resolveType = \parser ->
|
||||
when parser is
|
||||
Loc p -> loc [resolveType p]
|
||||
Specialize _err p -> resolveType p
|
||||
Record name _fields -> Items [ Import {modu: ["crate", "generated_ast"], name}, Text "<'a>"]
|
||||
Builtin _name ty -> ty
|
||||
|
||||
printCombinatorParser : Parser -> RenderTree
|
||||
printCombinatorParser = \parser ->
|
||||
when parser is
|
||||
Loc p ->
|
||||
printed = printCombinatorParser p
|
||||
value : RenderTree
|
||||
value = Items [ (Text "loc!("), printed, (Text ")") ]
|
||||
value
|
||||
Specialize err p ->
|
||||
printed = printCombinatorParser p
|
||||
Items [
|
||||
Import {modu: ["crate", "parser"], name: "specialize"},
|
||||
Text "(",
|
||||
err,
|
||||
(Text ", "),
|
||||
printed,
|
||||
(Text ")"),
|
||||
]
|
||||
Record name fields ->
|
||||
Items [
|
||||
Text "record!(\(name) {",
|
||||
(Indent
|
||||
(fields
|
||||
|> List.map \f ->
|
||||
Line [Text "\(f.name): ", printCombinatorParser f.parser, Text ","]
|
||||
)
|
||||
),
|
||||
Text "})"
|
||||
]
|
||||
Builtin name _ty -> name
|
||||
|
||||
printAst : Parser -> RenderTree
|
||||
printAst = \parser ->
|
||||
when parser is
|
||||
Record name fields ->
|
||||
Items [
|
||||
Line [ Text "#[derive(Clone, Debug, PartialEq)]" ],
|
||||
Line [ Text "pub struct \(name)<'a> {" ],
|
||||
(Indent (
|
||||
fields
|
||||
|> List.map \f ->
|
||||
Line [Text "pub \(f.name): ", resolveType f.parser, Text ","]
|
||||
)),
|
||||
Line [Text "}"],
|
||||
Line [Text ""],
|
||||
]
|
||||
_ -> crash "Not implemented"
|
||||
|
||||
expect (render (Text "foo")) == "foo"
|
||||
expect (render (Line [Text "foo"])) == "foo\n"
|
||||
expect (render (Indent [Text "foo"])) == " foo"
|
||||
expect (render (Line [Indent [Text "foo"]])) == " foo\n"
|
||||
|
||||
expect
|
||||
res = (render (Items [Text "{", Indent [Line [Text "foo"]], Text "}"]))
|
||||
res ==
|
||||
"""
|
||||
{
|
||||
foo
|
||||
}
|
||||
"""
|
||||
|
||||
allSyntaxItems = [interfaceHeader, hostedHeader]
|
||||
|
||||
printedAstItems = Items (allSyntaxItems |> List.map printAst)
|
||||
printedParserItems = Items (allSyntaxItems |> List.map printCombinatorParserFunction)
|
||||
|
||||
|
||||
# main : Task {} []*
|
||||
main =
|
||||
task =
|
||||
_ <- File.writeUtf8 (Path.fromStr "generated_ast.rs") (renderFile printedAstItems) |> Task.await
|
||||
|
||||
File.writeUtf8 (Path.fromStr "generated_parser.rs") (renderFile printedParserItems)
|
||||
|
||||
Task.attempt task \result ->
|
||||
when result is
|
||||
Ok _ -> Stdout.line "Success!"
|
||||
Err _e -> Stdout.line "Failed to write file"
|
@ -47,7 +47,7 @@ pub fn parse_header<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
||||
pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
||||
use crate::parser::keyword_e;
|
||||
|
||||
record!(Module {
|
||||
|
@ -1 +0,0 @@
|
||||
SourceError { problem: Imports(ListEnd(@87), @65), bytes: [97, 112, 112, 32, 34, 116, 101, 115, 116, 45, 109, 105, 115, 115, 105, 110, 103, 45, 99, 111, 109, 109, 97, 34, 10, 32, 32, 32, 32, 112, 97, 99, 107, 97, 103, 101, 115, 32, 123, 32, 112, 102, 58, 32, 34, 112, 108, 97, 116, 102, 111, 114, 109, 47, 109, 97, 105, 110, 46, 114, 111, 99, 34, 32, 125, 10, 32, 32, 32, 32, 105, 109, 112, 111, 114, 116, 115, 32, 91, 112, 102, 46, 84, 97, 115, 107, 32, 66, 97, 115, 101, 54, 52, 93, 10, 32, 32, 32, 32, 112, 114, 111, 118, 105, 100, 101, 115, 32, 91, 109, 97, 105, 110, 44, 32, 64, 70, 111, 111, 93, 32, 116, 111, 32, 112, 102] }
|
@ -1 +0,0 @@
|
||||
SourceError { problem: Space(HasMisplacedCarriageReturn, @1), bytes: [35, 13, 12, 9, 65] }
|
@ -1,41 +0,0 @@
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
Index(0),
|
||||
],
|
||||
regions: [
|
||||
@0-5,
|
||||
],
|
||||
space_before: [
|
||||
Slice(start = 0, length = 0),
|
||||
],
|
||||
space_after: [
|
||||
Slice(start = 0, length = 0),
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [
|
||||
Alias {
|
||||
header: TypeHeader {
|
||||
name: @0-1 "R",
|
||||
vars: [],
|
||||
},
|
||||
ann: @4-5 Apply(
|
||||
"",
|
||||
"D",
|
||||
[],
|
||||
),
|
||||
},
|
||||
],
|
||||
value_defs: [],
|
||||
},
|
||||
@7-8 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "a",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
@ -1,3 +0,0 @@
|
||||
R : D
|
||||
|
||||
a
|
@ -1,3 +0,0 @@
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
|
||||
1
|
@ -1,5 +0,0 @@
|
||||
Ab1 has ab1 : a -> {} | a has Ab1
|
||||
|
||||
Ab2 has ab2 : a -> {} | a has Ab2
|
||||
|
||||
1
|
@ -1,9 +0,0 @@
|
||||
## first line of docs
|
||||
## second line
|
||||
## third line
|
||||
## fourth line
|
||||
##
|
||||
## sixth line after doc new line
|
||||
x = 5
|
||||
|
||||
42
|
@ -1 +0,0 @@
|
||||
""
|
@ -1,4 +0,0 @@
|
||||
f : (Str)a -> (Str)a
|
||||
f = \x -> x
|
||||
|
||||
f ("Str", 42)
|
@ -1,4 +0,0 @@
|
||||
f : I64 -> (I64, I64)
|
||||
f = \x -> (x, x + 1)
|
||||
|
||||
f 42
|
@ -1,5 +0,0 @@
|
||||
my_list = [
|
||||
0,
|
||||
1,
|
||||
]
|
||||
42
|
@ -1,3 +0,0 @@
|
||||
when x is
|
||||
bar.and -> 1
|
||||
_ -> 4
|
@ -1,3 +0,0 @@
|
||||
when x is
|
||||
Foo.and -> 1
|
||||
_ -> 4
|
@ -1,3 +0,0 @@
|
||||
x, y <- List.map2 [] []
|
||||
|
||||
x + y
|
@ -1 +0,0 @@
|
||||
"foo"
|
@ -1,4 +0,0 @@
|
||||
x =
|
||||
5
|
||||
|
||||
42
|
@ -1 +0,0 @@
|
||||
"x"
|
@ -1,4 +0,0 @@
|
||||
# leading comment
|
||||
x = 5
|
||||
|
||||
42
|
@ -1 +0,0 @@
|
||||
@Age
|
@ -1 +0,0 @@
|
||||
@Age m n
|
@ -1,3 +0,0 @@
|
||||
Blah a b : Foo.Bar.Baz x y
|
||||
|
||||
42
|
@ -1,3 +0,0 @@
|
||||
foo : Foo.Bar.Baz x y as Blah a b
|
||||
|
||||
42
|
@ -1 +0,0 @@
|
||||
1 * if Bool.true then 1 else 1
|
@ -1,5 +0,0 @@
|
||||
# leading comment
|
||||
{ x, y } = 5
|
||||
y = 6
|
||||
|
||||
42
|
@ -1,3 +0,0 @@
|
||||
x : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str }
|
||||
|
||||
42
|
@ -1,5 +0,0 @@
|
||||
# leading comment
|
||||
x = 5
|
||||
y = 6
|
||||
|
||||
42
|
@ -1,9 +0,0 @@
|
||||
when x is
|
||||
_ ->
|
||||
1
|
||||
|
||||
_ ->
|
||||
2
|
||||
|
||||
Ok ->
|
||||
3
|
@ -1,3 +0,0 @@
|
||||
f : a -> (b -> c) | a has A
|
||||
|
||||
f
|
@ -1,3 +0,0 @@
|
||||
f : a -> (b -> c) | a has A, b has Eq, c has Ord
|
||||
|
||||
f
|
@ -1,3 +0,0 @@
|
||||
f : a | a has A
|
||||
|
||||
f
|
@ -1,3 +1,6 @@
|
||||
/// Simple tests for parsing expressions, module headers, module defs, etc.
|
||||
/// Note, much more extensive tests are in the `test_syntax` crate (in test_snapshots).
|
||||
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
@ -24,420 +27,8 @@ mod test_parse {
|
||||
use roc_parse::state::State;
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
use std::{f64, i64};
|
||||
|
||||
macro_rules! parse_snapshot_kind {
|
||||
(expr => $arena:expr, $input:expr) => {
|
||||
parse_expr_with($arena, $input.trim())
|
||||
};
|
||||
(header => $arena:expr, $input:expr) => {
|
||||
roc_parse::module::parse_header($arena, State::new($input.trim().as_bytes()))
|
||||
.map(|tuple| tuple.0)
|
||||
};
|
||||
(module => $arena:expr, $input:expr) => {
|
||||
module_defs()
|
||||
.parse($arena, State::new($input.as_bytes()), 0)
|
||||
.map(|tuple| tuple.1)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! should_pass {
|
||||
(pass) => {
|
||||
true
|
||||
};
|
||||
(fail) => {
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! snapshot_tests {
|
||||
(
|
||||
$($pass_or_fail:ident / $test_name:ident . $kind:ident),*
|
||||
$(,)?
|
||||
) => {
|
||||
#[test]
|
||||
fn no_extra_snapshot_test_files() {
|
||||
let tests = &[
|
||||
$(concat!(
|
||||
stringify!($pass_or_fail),
|
||||
"/",
|
||||
stringify!($test_name),
|
||||
".",
|
||||
stringify!($kind)
|
||||
)),*,
|
||||
].iter().map(|t| *t).collect::<std::collections::HashSet<&str>>();
|
||||
|
||||
fn list(dir: &std::path::Path) -> std::vec::Vec<String> {
|
||||
std::fs::read_dir(dir).unwrap().map(|f| f.unwrap().file_name().to_str().unwrap().to_string()).collect::<std::vec::Vec<_>>()
|
||||
}
|
||||
|
||||
let mut base = std::path::PathBuf::from("tests");
|
||||
base.push("snapshots");
|
||||
let pass_or_fail_names = list(&base);
|
||||
let mut extra_test_files = std::collections::HashSet::new();
|
||||
for res in pass_or_fail_names {
|
||||
assert!(res == "pass" || res == "fail", "a pass or fail filename was neither \"pass\" nor \"fail\", but rather: {:?}", res);
|
||||
let res_dir = base.join(&res);
|
||||
for file in list(&res_dir) {
|
||||
if file.ends_with(".expr.formatted.roc") {
|
||||
// These are written by fmt tests for verification. Ignore them!
|
||||
continue;
|
||||
}
|
||||
|
||||
let test = if let Some(test) = file.strip_suffix(".roc") {
|
||||
test
|
||||
} else if let Some(test) = file.strip_suffix(".result-ast") {
|
||||
test
|
||||
} else {
|
||||
panic!("unexpected file found in tests/snapshots: {}", file);
|
||||
};
|
||||
let test_name = format!("{}/{}", &res, test);
|
||||
if !tests.contains(test_name.as_str()) {
|
||||
extra_test_files.insert(test_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if extra_test_files.len() > 0 {
|
||||
eprintln!("Found extra test files:");
|
||||
for file in extra_test_files {
|
||||
eprintln!("{}", file);
|
||||
}
|
||||
panic!("Add entries for these in the `snapshot_tests!` macro in test_parse.rs");
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
snapshot_test(should_pass!($pass_or_fail), stringify!($test_name), stringify!($kind), |input| {
|
||||
let arena = Bump::new();
|
||||
let result = parse_snapshot_kind!($kind => &arena, input);
|
||||
result
|
||||
.map(|actual_ast| format!("{:#?}\n", actual_ast))
|
||||
.map_err(|error| format!("{:?}", error))
|
||||
});
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// see tests/snapshots to see test input(.roc) and expected output(.result-ast)
|
||||
snapshot_tests! {
|
||||
fail/ability_demand_value_has_args.expr,
|
||||
fail/ability_demands_not_indented_with_first.expr,
|
||||
fail/ability_first_demand_not_indented_enough.expr,
|
||||
fail/ability_non_signature_expression.expr,
|
||||
fail/alias_or_opaque_fail.expr,
|
||||
fail/comment_with_tab.expr,
|
||||
fail/def_missing_final_expression.expr,
|
||||
fail/double_plus.expr,
|
||||
fail/elm_function_syntax.expr,
|
||||
fail/empty_or_pattern.expr,
|
||||
fail/error_inline_alias_argument_uppercase.expr,
|
||||
fail/error_inline_alias_not_an_alias.expr,
|
||||
fail/error_inline_alias_qualified.expr,
|
||||
fail/expr_to_pattern_fail.expr,
|
||||
fail/expression_indentation_end.expr,
|
||||
fail/if_guard_without_condition.expr,
|
||||
fail/if_missing_else.expr,
|
||||
fail/if_outdented_then.expr,
|
||||
fail/imports_missing_comma.header,
|
||||
fail/inline_hastype.expr,
|
||||
fail/invalid_operator.expr,
|
||||
fail/lambda_double_comma.expr,
|
||||
fail/lambda_extra_comma.expr,
|
||||
fail/lambda_leading_comma.expr,
|
||||
fail/lambda_missing_indent.expr,
|
||||
fail/list_double_comma.expr,
|
||||
fail/list_pattern_not_terminated.expr,
|
||||
fail/list_pattern_weird_indent.expr,
|
||||
fail/list_pattern_weird_rest_pattern.expr,
|
||||
fail/list_without_end.expr,
|
||||
fail/multi_no_end.expr,
|
||||
fail/pattern_binds_keyword.expr,
|
||||
fail/pattern_in_parens_end_comma.expr,
|
||||
fail/pattern_in_parens_end.expr,
|
||||
fail/pattern_in_parens_indent_open.expr,
|
||||
fail/pattern_in_parens_open.expr,
|
||||
fail/record_type_end.expr,
|
||||
fail/record_type_keyword_field_name.expr,
|
||||
fail/record_type_missing_comma.expr,
|
||||
fail/record_type_open_indent.expr,
|
||||
fail/record_type_open.expr,
|
||||
fail/record_type_tab.expr,
|
||||
fail/single_no_end.expr,
|
||||
fail/tab_crash.header,
|
||||
fail/tag_union_end.expr,
|
||||
fail/tag_union_lowercase_tag_name.expr,
|
||||
fail/tag_union_open.expr,
|
||||
fail/tag_union_second_lowercase_tag_name.expr,
|
||||
fail/trailing_operator.expr,
|
||||
fail/type_annotation_double_colon.expr,
|
||||
fail/type_apply_stray_dot.expr,
|
||||
fail/type_argument_arrow_then_nothing.expr,
|
||||
fail/type_argument_no_arrow.expr,
|
||||
fail/type_double_comma.expr,
|
||||
fail/type_in_parens_end.expr,
|
||||
fail/type_in_parens_start.expr,
|
||||
fail/type_inline_alias.expr,
|
||||
fail/underscore_name_type_annotation.expr,
|
||||
fail/unfinished_closure_pattern_in_parens.expr,
|
||||
fail/unicode_not_hex.expr,
|
||||
fail/weird_escape.expr,
|
||||
fail/when_missing_arrow.expr,
|
||||
fail/when_outdented_branch.expr,
|
||||
fail/when_over_indented_int.expr,
|
||||
fail/when_over_indented_underscore.expr,
|
||||
fail/wild_case_arrow.expr,
|
||||
pass/ability_demand_signature_is_multiline.expr,
|
||||
pass/ability_multi_line.expr,
|
||||
pass/ability_single_line.expr,
|
||||
pass/ability_two_in_a_row.expr,
|
||||
pass/add_var_with_spaces.expr,
|
||||
pass/add_with_spaces.expr,
|
||||
pass/annotated_record_destructure.expr,
|
||||
pass/annotated_tag_destructure.expr,
|
||||
pass/annotated_tuple_destructure.expr,
|
||||
pass/apply_parenthetical_tag_args.expr,
|
||||
pass/apply_tag.expr,
|
||||
pass/apply_three_args.expr,
|
||||
pass/apply_two_args.expr,
|
||||
pass/apply_unary_negation.expr,
|
||||
pass/apply_unary_not.expr,
|
||||
pass/bad_opaque_ref.expr,
|
||||
pass/basic_apply.expr,
|
||||
pass/basic_docs.expr,
|
||||
pass/basic_field.expr,
|
||||
pass/basic_tag.expr,
|
||||
pass/basic_tuple.expr,
|
||||
pass/basic_var.expr,
|
||||
pass/closure_with_underscores.expr,
|
||||
pass/comment_after_def.module,
|
||||
pass/comment_after_op.expr,
|
||||
pass/comment_before_op.expr,
|
||||
pass/comment_inside_empty_list.expr,
|
||||
pass/comment_with_non_ascii.expr,
|
||||
pass/crash.expr,
|
||||
pass/dbg.expr,
|
||||
pass/def_without_newline.expr,
|
||||
pass/destructure_tag_assignment.expr,
|
||||
pass/empty_app_header.header,
|
||||
pass/empty_hosted_header.header,
|
||||
pass/empty_interface_header.header,
|
||||
pass/empty_list.expr,
|
||||
pass/empty_package_header.header,
|
||||
pass/empty_platform_header.header,
|
||||
pass/empty_record.expr,
|
||||
pass/empty_string.expr,
|
||||
pass/equals_with_spaces.expr,
|
||||
pass/equals.expr,
|
||||
pass/expect_fx.module,
|
||||
pass/expect.expr,
|
||||
pass/float_with_underscores.expr,
|
||||
pass/full_app_header_trailing_commas.header,
|
||||
pass/full_app_header.header,
|
||||
pass/function_effect_types.header,
|
||||
pass/function_with_tuple_ext_type.expr,
|
||||
pass/function_with_tuple_type.expr,
|
||||
pass/highest_float.expr,
|
||||
pass/highest_int.expr,
|
||||
pass/if_def.expr,
|
||||
pass/int_with_underscore.expr,
|
||||
pass/interface_with_newline.header,
|
||||
pass/lambda_in_chain.expr,
|
||||
pass/lambda_indent.expr,
|
||||
pass/list_closing_indent_not_enough.expr,
|
||||
pass/list_closing_same_indent_no_trailing_comma.expr,
|
||||
pass/list_closing_same_indent_with_trailing_comma.expr,
|
||||
pass/list_patterns.expr,
|
||||
pass/lowest_float.expr,
|
||||
pass/lowest_int.expr,
|
||||
pass/malformed_ident_due_to_underscore.expr,
|
||||
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
|
||||
pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399
|
||||
pass/minimal_app_header.header,
|
||||
pass/minus_twelve_minus_five.expr,
|
||||
pass/mixed_docs.expr,
|
||||
pass/module_def_newline.module,
|
||||
pass/multi_backpassing.expr,
|
||||
pass/multi_char_string.expr,
|
||||
pass/multiline_string.expr,
|
||||
pass/multiline_tuple_with_comments.expr,
|
||||
pass/multiline_type_signature_with_comment.expr,
|
||||
pass/multiline_type_signature.expr,
|
||||
pass/multiple_fields.expr,
|
||||
pass/multiple_operators.expr,
|
||||
pass/neg_inf_float.expr,
|
||||
pass/negative_float.expr,
|
||||
pass/negative_int.expr,
|
||||
pass/nested_def_annotation.module,
|
||||
pass/nested_if.expr,
|
||||
pass/nested_module.header,
|
||||
pass/newline_after_equals.expr, // Regression test for https://github.com/roc-lang/roc/issues/51
|
||||
pass/newline_after_mul.expr,
|
||||
pass/newline_after_sub.expr,
|
||||
pass/newline_and_spaces_before_less_than.expr,
|
||||
pass/newline_before_add.expr,
|
||||
pass/newline_before_sub.expr,
|
||||
pass/newline_inside_empty_list.expr,
|
||||
pass/newline_singleton_list.expr,
|
||||
pass/nonempty_hosted_header.header,
|
||||
pass/nonempty_package_header.header,
|
||||
pass/nonempty_platform_header.header,
|
||||
pass/not_docs.expr,
|
||||
pass/number_literal_suffixes.expr,
|
||||
pass/one_backpassing.expr,
|
||||
pass/one_char_string.expr,
|
||||
pass/one_def.expr,
|
||||
pass/one_minus_two.expr,
|
||||
pass/one_plus_two.expr,
|
||||
pass/one_spaced_def.expr,
|
||||
pass/opaque_destructure_first_item_in_body.expr,
|
||||
pass/opaque_has_abilities.expr,
|
||||
pass/opaque_reference_expr_with_arguments.expr,
|
||||
pass/opaque_reference_expr.expr,
|
||||
pass/opaque_reference_pattern_with_arguments.expr,
|
||||
pass/opaque_reference_pattern.expr,
|
||||
pass/opaque_simple.module,
|
||||
pass/opaque_with_type_arguments.module,
|
||||
pass/ops_with_newlines.expr,
|
||||
pass/outdented_app_with_record.expr,
|
||||
pass/outdented_list.expr,
|
||||
pass/outdented_record.expr,
|
||||
pass/packed_singleton_list.expr,
|
||||
pass/parenthetical_apply.expr,
|
||||
pass/parenthetical_basic_field.expr,
|
||||
pass/parenthetical_field_qualified_var.expr,
|
||||
pass/parenthetical_var.expr,
|
||||
pass/parse_alias.expr,
|
||||
pass/parse_as_ann.expr,
|
||||
pass/pattern_with_space_in_parens.expr, // https://github.com/roc-lang/roc/issues/929
|
||||
pass/plus_if.expr,
|
||||
pass/plus_when.expr,
|
||||
pass/pos_inf_float.expr,
|
||||
pass/positive_float.expr,
|
||||
pass/positive_int.expr,
|
||||
pass/provides_type.header,
|
||||
pass/qualified_field.expr,
|
||||
pass/qualified_tag.expr,
|
||||
pass/qualified_var.expr,
|
||||
pass/record_access_after_tuple.expr,
|
||||
pass/record_destructure_def.expr,
|
||||
pass/record_func_type_decl.expr,
|
||||
pass/record_type_with_function.expr,
|
||||
pass/record_update.expr,
|
||||
pass/record_with_if.expr,
|
||||
pass/requires_type.header,
|
||||
pass/single_arg_closure.expr,
|
||||
pass/single_underscore_closure.expr,
|
||||
pass/space_only_after_minus.expr,
|
||||
pass/spaced_singleton_list.expr,
|
||||
pass/spaces_inside_empty_list.expr,
|
||||
pass/standalone_module_defs.module,
|
||||
pass/string_without_escape.expr,
|
||||
pass/sub_var_with_spaces.expr,
|
||||
pass/sub_with_spaces.expr,
|
||||
pass/tag_pattern.expr,
|
||||
pass/ten_times_eleven.expr,
|
||||
pass/three_arg_closure.expr,
|
||||
pass/tuple_access_after_record.expr,
|
||||
pass/tuple_accessor_function.expr,
|
||||
pass/tuple_type_ext.expr,
|
||||
pass/tuple_type.expr,
|
||||
pass/two_arg_closure.expr,
|
||||
pass/two_backpassing.expr,
|
||||
pass/two_branch_when.expr,
|
||||
pass/two_spaced_def.expr,
|
||||
pass/abcdplain.expr,
|
||||
pass/abcdfmt.expr,
|
||||
pass/type_decl_with_underscore.expr,
|
||||
pass/unary_negation_access.expr, // Regression test for https://github.com/roc-lang/roc/issues/509
|
||||
pass/unary_negation_arg.expr,
|
||||
pass/unary_negation_with_parens.expr,
|
||||
pass/unary_negation.expr,
|
||||
pass/unary_not_with_parens.expr,
|
||||
pass/unary_not.expr,
|
||||
pass/underscore_backpassing.expr,
|
||||
pass/underscore_in_assignment_pattern.expr,
|
||||
pass/var_else.expr,
|
||||
pass/var_if.expr,
|
||||
pass/var_is.expr,
|
||||
pass/var_minus_two.expr,
|
||||
pass/var_then.expr,
|
||||
pass/var_when.expr,
|
||||
pass/when_if_guard.expr,
|
||||
pass/when_in_assignment.expr,
|
||||
pass/when_in_function_python_style_indent.expr,
|
||||
pass/when_in_function.expr,
|
||||
pass/when_in_parens_indented.expr,
|
||||
pass/when_in_parens.expr,
|
||||
pass/when_with_alternative_patterns.expr,
|
||||
pass/when_with_function_application.expr,
|
||||
pass/when_with_negative_numbers.expr,
|
||||
pass/when_with_numbers.expr,
|
||||
pass/when_with_records.expr,
|
||||
pass/when_with_tuple_in_record.expr,
|
||||
pass/when_with_tuples.expr,
|
||||
pass/where_clause_function.expr,
|
||||
pass/where_clause_multiple_bound_abilities.expr,
|
||||
pass/where_clause_multiple_has_across_newlines.expr,
|
||||
pass/where_clause_multiple_has.expr,
|
||||
pass/where_clause_non_function.expr,
|
||||
pass/where_clause_on_newline.expr,
|
||||
pass/zero_float.expr,
|
||||
pass/zero_int.expr,
|
||||
}
|
||||
|
||||
fn snapshot_test(
|
||||
should_pass: bool,
|
||||
name: &str,
|
||||
ty: &str,
|
||||
func: impl Fn(&str) -> Result<String, String>,
|
||||
) {
|
||||
let mut parent = std::path::PathBuf::from("tests");
|
||||
parent.push("snapshots");
|
||||
parent.push(if should_pass { "pass" } else { "fail" });
|
||||
let input_path = parent.join(&format!("{}.{}.roc", name, ty));
|
||||
let result_path = parent.join(&format!("{}.{}.result-ast", name, ty));
|
||||
|
||||
let input = std::fs::read_to_string(&input_path).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not find a snapshot test result at {:?} - {:?}",
|
||||
input_path, err
|
||||
)
|
||||
});
|
||||
|
||||
let result = func(&input);
|
||||
|
||||
let actual_result = if should_pass {
|
||||
result.expect("The source code for this test did not successfully parse!")
|
||||
} else {
|
||||
result.expect_err(
|
||||
"The source code for this test successfully parsed, but it was not expected to!",
|
||||
)
|
||||
};
|
||||
|
||||
if std::env::var("ROC_SNAPSHOT_TEST_OVERWRITE").is_ok() {
|
||||
std::fs::write(&result_path, actual_result).unwrap();
|
||||
} else {
|
||||
let expected_result = std::fs::read_to_string(&result_path).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error opening test output file {}:\n\
|
||||
{:?}
|
||||
Supposing the file is missing, consider running the tests with:\n\
|
||||
`env ROC_SNAPSHOT_TEST_OVERWRITE=1 cargo test ...`\n\
|
||||
and committing the file that creates.",
|
||||
result_path.display(),
|
||||
e
|
||||
);
|
||||
});
|
||||
|
||||
assert_multiline_str_eq!(expected_result, actual_result);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
||||
let arena = Bump::new();
|
||||
let actual = parse_expr_with(&arena, input.trim());
|
||||
@ -667,207 +258,6 @@ mod test_parse {
|
||||
assert_parses_to("'b'", Expr::SingleQuote("b"));
|
||||
}
|
||||
|
||||
// RECORD LITERALS
|
||||
|
||||
// #[test]
|
||||
// fn type_signature_def() {
|
||||
// let arena = Bump::new();
|
||||
// let newline = bumpalo::vec![in &arena; Newline];
|
||||
// let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
// let applied_ann = TypeAnnotation::Apply("", "Int", &[]);
|
||||
// let signature = Def::Annotation(
|
||||
// Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
// Located::new(0, 0, 6, 9, applied_ann),
|
||||
// );
|
||||
// let def = Def::Body(
|
||||
// arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
// arena.alloc(Located::new(1, 1, 6, 7, Num("4"))),
|
||||
// );
|
||||
// let spaced_def = Def::SpaceBefore(arena.alloc(def), &newline);
|
||||
// let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def));
|
||||
|
||||
// let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
// let defs = &[loc_ann, loc_def];
|
||||
// let ret = Expr::SpaceBefore(arena.alloc(Num("42")), &newlines);
|
||||
// let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
// let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
// assert_parses_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// foo : Int
|
||||
// foo = 4
|
||||
|
||||
// 42
|
||||
// "#
|
||||
// ),
|
||||
// expected,
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn type_signature_function_def() {
|
||||
// use TypeAnnotation;
|
||||
// let arena = Bump::new();
|
||||
// let newline = bumpalo::vec![in &arena; Newline];
|
||||
// let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
|
||||
// let int_type = TypeAnnotation::Apply("", "Int", &[]);
|
||||
// let float_type = TypeAnnotation::Apply("", "Float", &[]);
|
||||
// let bool_type = TypeAnnotation::Apply("", "Bool", &[]);
|
||||
|
||||
// let arguments = bumpalo::vec![in &arena;
|
||||
// Located::new(0, 0, 6, 9, int_type),
|
||||
// Located::new(0, 0, 11, 16, float_type)
|
||||
// ];
|
||||
// let return_type = Located::new(0, 0, 20, 24, bool_type);
|
||||
// let fn_ann = TypeAnnotation::Function(&arguments, &return_type);
|
||||
// let signature = Def::Annotation(
|
||||
// Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
// Located::new(0, 0, 20, 24, fn_ann),
|
||||
// );
|
||||
|
||||
// let args = bumpalo::vec![in &arena;
|
||||
// Located::new(1,1,7,8, Identifier("x")),
|
||||
// Located::new(1,1,10,11, Underscore)
|
||||
// ];
|
||||
// let body = Located::new(1, 1, 15, 17, Num("42"));
|
||||
|
||||
// let closure = Expr::Closure(&args, &body);
|
||||
|
||||
// let def = Def::Body(
|
||||
// arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
// arena.alloc(Located::new(1, 1, 6, 17, closure)),
|
||||
// );
|
||||
// let spaced = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
// let loc_def = &*arena.alloc(Located::new(1, 1, 0, 17, spaced));
|
||||
|
||||
// let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
// let defs = &[loc_ann, loc_def];
|
||||
// let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
|
||||
// let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
// let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
// assert_parses_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// foo : Int, Float -> Bool
|
||||
// foo = \x, _ -> 42
|
||||
|
||||
// 42
|
||||
// "#
|
||||
// ),
|
||||
// expected,
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn ann_open_union() {
|
||||
// let arena = Bump::new();
|
||||
// let newline = bumpalo::vec![in &arena; Newline];
|
||||
// let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
// let tag1 = Tag::Apply {
|
||||
// name: Located::new(0, 0, 8, 12, "True"),
|
||||
// args: &[],
|
||||
// };
|
||||
// let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||||
// let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
// let tag2 = Tag::Apply {
|
||||
// name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
// args: tag2args.into_bump_slice(),
|
||||
// };
|
||||
// let tags = bumpalo::vec![in &arena;
|
||||
// Located::new(0, 0, 8, 12, tag1),
|
||||
// Located::new(0, 0, 14, 27, tag2)
|
||||
// ];
|
||||
// let loc_wildcard = Located::new(0, 0, 29, 30, TypeAnnotation::Wildcard);
|
||||
// let applied_ann = TypeAnnotation::TagUnion {
|
||||
// tags: tags.into_bump_slice(),
|
||||
// ext: Some(arena.alloc(loc_wildcard)),
|
||||
// };
|
||||
// let signature = Def::Annotation(
|
||||
// Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
// Located::new(0, 0, 6, 30, applied_ann),
|
||||
// );
|
||||
// let def = Def::Body(
|
||||
// arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
// arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))),
|
||||
// );
|
||||
// let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
// let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
// let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
// let defs = &[loc_ann, loc_def];
|
||||
// let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
|
||||
// let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
// let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
// assert_parses_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// foo : [True, Perhaps Thing]*
|
||||
// foo = True
|
||||
|
||||
// 42
|
||||
// "#
|
||||
// ),
|
||||
// expected,
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn ann_closed_union() {
|
||||
// let arena = Bump::new();
|
||||
// let newline = bumpalo::vec![in &arena; Newline];
|
||||
// let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
// let tag1 = Tag::Apply {
|
||||
// name: Located::new(0, 0, 8, 12, "True"),
|
||||
// args: &[],
|
||||
// };
|
||||
// let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||||
// let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
// let tag2 = Tag::Apply {
|
||||
// name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
// args: tag2args.into_bump_slice(),
|
||||
// };
|
||||
// let tags = bumpalo::vec![in &arena;
|
||||
// Located::new(0, 0, 8, 12, tag1),
|
||||
// Located::new(0, 0, 14, 27, tag2)
|
||||
// ];
|
||||
// let applied_ann = TypeAnnotation::TagUnion {
|
||||
// tags: tags.into_bump_slice(),
|
||||
// ext: None,
|
||||
// };
|
||||
// let signature = Def::Annotation(
|
||||
// Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
// Located::new(0, 0, 6, 29, applied_ann),
|
||||
// );
|
||||
// let def = Def::Body(
|
||||
// arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
// arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))),
|
||||
// );
|
||||
// let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
// let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
// let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
// let defs = &[loc_ann, loc_def];
|
||||
// let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
|
||||
// let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
// let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
// assert_parses_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// foo : [True, Perhaps Thing]
|
||||
// foo = True
|
||||
|
||||
// 42
|
||||
// "#
|
||||
// ),
|
||||
// expected,
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn repro_keyword_bug() {
|
||||
// Reproducing this bug requires a bizarre set of things to all be true:
|
||||
|
21
crates/compiler/test_syntax/Cargo.toml
Normal file
21
crates/compiler/test_syntax/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "test_syntax"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Tests for the parse + fmt crates."
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_fmt = { path = "../fmt" }
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
bumpalo.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
walkdir.workspace = true
|
@ -253,12 +253,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.131"
|
||||
@ -450,24 +444,11 @@ name = "roc_fmt"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_test_utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_fmt-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"libfuzzer-sys",
|
||||
"roc_fmt",
|
||||
"roc_parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -519,15 +500,6 @@ version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -590,6 +562,28 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "test_syntax"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
"roc_fmt",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_test_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_syntax-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"libfuzzer-sys",
|
||||
"test_syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
@ -608,17 +602,6 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -641,15 +624,6 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "roc_parse-fuzz"
|
||||
name = "test_syntax-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
@ -11,9 +11,7 @@ cargo-fuzz = true
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
|
||||
[dependencies.roc_parse]
|
||||
path = ".."
|
||||
test_syntax = { path = "../../test_syntax" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
@ -26,13 +24,7 @@ test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_defs"
|
||||
path = "fuzz_targets/fuzz_defs.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_header"
|
||||
path = "fuzz_targets/fuzz_header.rs"
|
||||
name = "fuzz_module"
|
||||
path = "fuzz_targets/fuzz_module.rs"
|
||||
test = false
|
||||
doc = false
|
@ -1,11 +1,14 @@
|
||||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
use bumpalo::Bump;
|
||||
use test_syntax::test_helpers::Input;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let input = Input::Expr(input);
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_expr_with(&arena, input.trim());
|
||||
if input.parse_in(&arena).is_ok() {
|
||||
input.check_invariants(|_| (), true);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,11 +1,14 @@
|
||||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_defs_with;
|
||||
use bumpalo::Bump;
|
||||
use test_syntax::test_helpers::Input;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let input = Input::Full(input);
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_defs_with(&arena, input.trim());
|
||||
if input.parse_in(&arena).is_ok() {
|
||||
input.check_invariants(|_| (), true);
|
||||
}
|
||||
}
|
||||
});
|
1
crates/compiler/test_syntax/src/lib.rs
Normal file
1
crates/compiler/test_syntax/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod test_helpers;
|
239
crates/compiler/test_syntax/src/test_helpers.rs
Normal file
239
crates/compiler/test_syntax/src/test_helpers.rs
Normal file
@ -0,0 +1,239 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_fmt::{annotation::Formattable, module::fmt_module};
|
||||
use roc_parse::{
|
||||
ast::{Defs, Expr, Module},
|
||||
module::module_defs,
|
||||
parser::{Parser, SyntaxError},
|
||||
state::State,
|
||||
test_helpers::{parse_defs_with, parse_expr_with, parse_header_with},
|
||||
};
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
use roc_fmt::Buf;
|
||||
|
||||
/// Source code to parse. Usually in the form of a test case.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Input<'a> {
|
||||
/// A header (e.g. `interface "foo" ...`)
|
||||
Header(&'a str),
|
||||
|
||||
/// A sequence of module definitions (e.g. `f = \x -> x + 1`)
|
||||
ModuleDefs(&'a str),
|
||||
|
||||
/// A single expression
|
||||
Expr(&'a str),
|
||||
|
||||
/// Both the header and the module defs
|
||||
Full(&'a str),
|
||||
}
|
||||
|
||||
// Owned version of `Input`
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InputOwned {
|
||||
Header(String),
|
||||
ModuleDefs(String),
|
||||
Expr(String),
|
||||
Full(String),
|
||||
}
|
||||
|
||||
impl InputOwned {
|
||||
fn as_ref(&self) -> Input {
|
||||
match self {
|
||||
InputOwned::Header(s) => Input::Header(s),
|
||||
InputOwned::ModuleDefs(s) => Input::ModuleDefs(s),
|
||||
InputOwned::Expr(s) => Input::Expr(s),
|
||||
InputOwned::Full(s) => Input::Full(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Output AST of a successful parse
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Output<'a> {
|
||||
Header(Module<'a>),
|
||||
|
||||
ModuleDefs(Defs<'a>),
|
||||
|
||||
Expr(Expr<'a>),
|
||||
|
||||
Full {
|
||||
header: Module<'a>,
|
||||
module_defs: Defs<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Output<'a> {
|
||||
fn format(&self) -> InputOwned {
|
||||
let arena = Bump::new();
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
match self {
|
||||
Output::Header(header) => {
|
||||
fmt_module(&mut buf, header);
|
||||
buf.fmt_end_of_file();
|
||||
InputOwned::Header(buf.as_str().to_string())
|
||||
}
|
||||
Output::ModuleDefs(defs) => {
|
||||
defs.format(&mut buf, 0);
|
||||
buf.fmt_end_of_file();
|
||||
InputOwned::ModuleDefs(buf.as_str().to_string())
|
||||
}
|
||||
Output::Expr(expr) => {
|
||||
expr.format(&mut buf, 0);
|
||||
InputOwned::Expr(buf.as_str().to_string())
|
||||
}
|
||||
Output::Full {
|
||||
header,
|
||||
module_defs,
|
||||
} => {
|
||||
fmt_module(&mut buf, header);
|
||||
module_defs.format(&mut buf, 0);
|
||||
buf.fmt_end_of_file();
|
||||
InputOwned::Full(buf.as_str().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_format_inner(&self) -> String {
|
||||
match self {
|
||||
Output::Header(header) => format!("{:#?}\n", header),
|
||||
Output::ModuleDefs(defs) => format!("{:#?}\n", defs),
|
||||
Output::Expr(expr) => format!("{:#?}\n", expr),
|
||||
Output::Full { .. } => format!("{:#?}\n", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Output<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
Output::Header(header) => Output::Header(header.remove_spaces(arena)),
|
||||
Output::ModuleDefs(defs) => Output::ModuleDefs(defs.remove_spaces(arena)),
|
||||
Output::Expr(expr) => Output::Expr(expr.remove_spaces(arena)),
|
||||
Output::Full {
|
||||
header,
|
||||
module_defs,
|
||||
} => Output::Full {
|
||||
header: header.remove_spaces(arena),
|
||||
module_defs: module_defs.remove_spaces(arena),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Input<'a> {
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
match self {
|
||||
Input::Header(s) => s,
|
||||
Input::ModuleDefs(s) => s,
|
||||
Input::Expr(s) => s,
|
||||
Input::Full(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_in(&self, arena: &'a Bump) -> Result<Output<'a>, SyntaxError<'a>> {
|
||||
match self {
|
||||
Input::Header(input) => {
|
||||
let header = parse_header_with(arena, input)?;
|
||||
Ok(Output::Header(header))
|
||||
}
|
||||
|
||||
Input::ModuleDefs(input) => {
|
||||
let module_defs = parse_defs_with(arena, input)?;
|
||||
Ok(Output::ModuleDefs(module_defs))
|
||||
}
|
||||
|
||||
Input::Expr(input) => {
|
||||
let expr = parse_expr_with(arena, input)?;
|
||||
Ok(Output::Expr(expr))
|
||||
}
|
||||
|
||||
Input::Full(input) => {
|
||||
let state = State::new(input.as_bytes());
|
||||
|
||||
let min_indent = 0;
|
||||
let (_, header, state) = roc_parse::module::header()
|
||||
.parse(arena, state.clone(), min_indent)
|
||||
.map_err(|(_, fail)| SyntaxError::Header(fail))?;
|
||||
|
||||
let (_, module_defs, _state) = module_defs()
|
||||
.parse(arena, state, min_indent)
|
||||
.map_err(|(_, fail)| fail)?;
|
||||
|
||||
Ok(Output::Full {
|
||||
header,
|
||||
module_defs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and re-format the given input, and pass the output to `check_formatting`
|
||||
/// for verification. The expectation is that `check_formatting` assert the result matches
|
||||
/// expectations (or, overwrite the expectation based on a command-line flag)
|
||||
/// Optionally, based on the value of `check_idempotency`, also verify that the formatting
|
||||
/// is idempotent - that if we reformat the output, we get the same result.
|
||||
pub fn check_invariants(
|
||||
&self,
|
||||
handle_formatted_output: impl Fn(Input),
|
||||
check_idempotency: bool,
|
||||
) {
|
||||
let arena = Bump::new();
|
||||
|
||||
let actual = self.parse_in(&arena).unwrap_or_else(|err| {
|
||||
panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", self.as_str(), err);
|
||||
});
|
||||
|
||||
let output = actual.format();
|
||||
|
||||
handle_formatted_output(output.as_ref());
|
||||
|
||||
let reparsed_ast = output.as_ref().parse_in(&arena).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\n\
|
||||
Parse error was: {:?}\n\n\
|
||||
The original code was:\n\n{}\n\n\
|
||||
The code that failed to parse:\n\n{}\n\n\
|
||||
The original ast was:\n\n{:#?}\n\n",
|
||||
err,
|
||||
self.as_str(),
|
||||
output.as_ref().as_str(),
|
||||
actual
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n\
|
||||
* * * AST before formatting:\n{:#?}\n\n\
|
||||
* * * AST after formatting:\n{:#?}\n\n",
|
||||
self.as_str(),
|
||||
output.as_ref().as_str(),
|
||||
ast_normalized,
|
||||
reparsed_ast_normalized
|
||||
);
|
||||
}
|
||||
|
||||
// Now verify that the resultant formatting is _idempotent_ - i.e. that it doesn't change again if re-formatted
|
||||
if check_idempotency {
|
||||
let reformatted = reparsed_ast.format();
|
||||
|
||||
if output != reformatted {
|
||||
eprintln!("Formatting bug; formatting is not stable.\nOriginal code:\n{}\n\nFormatted code:\n{}\n\n", self.as_str(), output.as_ref().as_str());
|
||||
eprintln!("Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output.as_ref().as_str(), reformatted.as_ref().as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
Header(Imports(ListEnd(@87), @65))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user